diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..236a6f1da9 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + project: + default: + threshold: 1% diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..764cd26a9c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" + - package-ecosystem: "cargo" + directory: "/fuzz/" + schedule: + interval: "weekly" + target-branch: "main" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 14286bddee..55d575a99a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,6 @@ ### Thanks for contributing to chrono! -- [ ] Have you added yourself and the change to the [changelog]? (Don't worry - about adding the PR number) -- [ ] If this pull request fixes a bug, does it add a test that verifies that - we can't reintroduce it? +If your feature is semver-compatible, please target the main branch; +for semver-incompatible changes, please target the `0.5.x` branch. -[changelog]: ../CHANGELOG.md +Please consider adding a test to ensure your bug fix/feature will not break in the future. diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000000..9caae21012 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,36 @@ +name: codecov + +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation serde arbitrary" + +on: + push: + branches: [main, 0.5.x] + pull_request: +jobs: + # Run code coverage using cargo-llvm-cov then upload to codecov.io + job_code_coverage: + name: llvm-cov + runs-on: ubuntu-latest + environment: Coverage + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v7 + # nightly is required for --doctests, see cargo-llvm-cov#2 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: rustfmt + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Generate code coverage + run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v6 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: lcov.info + fail_ci_if_error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c87878f1f6..bc6bf4a813 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,26 +1,66 @@ name: lint +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation serde arbitrary" + on: push: - branches: [main, master] + branches: [main, 0.5.x] pull_request: - branches: [main, master] jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --check -- --color=always + - run: cargo fmt --check --manifest-path fuzz/Cargo.toml + - run: cargo fmt --check --manifest-path bench/Cargo.toml + - run: | + cargo clippy ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --all-targets --color=always \ + -- -D warnings + - run: | + cargo clippy --target=x86_64-pc-windows-msvc --all-targets --color=always \ + -- -D warnings + - run: | + cargo clippy --manifest-path fuzz/Cargo.toml --all-targets --color=always \ + -- -D warnings + - run: | + cargo clippy --manifest-path bench/Cargo.toml --all-targets --color=always \ + -- -D warnings + env: + RUSTFLAGS: "-Dwarnings" + + cargo-deny: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: EmbarkStudios/cargo-deny-action@v2 + + check-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-deadlinks + - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} + - run: cargo doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps + env: + RUSTDOCFLAGS: -Dwarnings + + cffconvert: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 with: - toolchain: stable - override: true - - uses: Swatinem/rust-cache@v1 - - name: Lint - run: bash ci/lint.sh - - name: Run ShellCheck - uses: ludeeus/action-shellcheck@0.3.0 + persist-credentials: false + - uses: citation-file-format/cffconvert-github-action@2.0.0 with: - check_together: 'y' + args: --validate diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml deleted file mode 100644 index 5d57685fa1..0000000000 --- a/.github/workflows/test-release.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Release Test - -on: - push: - branches: ['rel*'] - pull_request: - branches: ['rel*'] - -# From here down this should be exactly the same as test.yml - -jobs: - test: - strategy: - matrix: - os: [ubuntu-18.04, ubuntu-latest, macos-latest, windows-latest] - rust_version: [stable] - include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-18.04 - rust_version: 1.13.0 - - os: macos-latest - rust_version: 1.13.0 - # time doesn't work on windows with 1.13 - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust_version }} - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: ${{ matrix.rust_version }} - EXHAUSTIVE_TZ: ${{ matrix.exhaustive_tz }} - - no_std: - strategy: - matrix: - os: [macos-latest, ubuntu-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust with no_std toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv6m-none-eabi - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build no_std lib - run: cargo build --target thumbv6m-none-eabi --color=always - working-directory: ./ci/core-test - - wasm: - strategy: - matrix: - os: [macos-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Install node - uses: actions/setup-node@v1 - with: - node-version: '12' - - - name: Install wasm-pack - run: | - export RUST_BACKTRACE=1 - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - wasm-pack --version - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_simple diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e439fe7cc2..06bddd1210 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,154 +1,194 @@ name: All Tests and Builds +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-32 rkyv-validation serde arbitrary" + on: push: - branches: [main] + branches: [main, 0.5.x] pull_request: - branches: [main] - paths: - - '**.rs' - - .github/** - - Cargo.toml jobs: - test: + timezones: strategy: matrix: - os: [ubuntu-18.04, ubuntu-latest, macos-latest, windows-latest] - rust_version: [stable] - exhaustive_tz: [onetz] - check_combinatoric: [no_combinatoric] - include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # compilation check - - os: ubuntu-latest - rust_version: stable - check_combinatoric: 'combinatoric' - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-18.04 - rust_version: 1.13.0 - - os: macos-latest - rust_version: 1.13.0 - # time doesn't work on windows with 1.13 - + os: [ubuntu-latest, macos-latest, windows-latest] + tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - run: cargo test ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always + + # later this may be able to be included with the below + # kept separate for now as the following don't compile on 1.60 + # * arbitrary (requires 1.63 as of v1.3.0) + rust_msrv: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.62.0" + - uses: Swatinem/rust-cache@v2 + # run --lib and --doc to avoid the long running integration tests + # which are run elsewhere + - run: | + cargo check --lib --features unstable-locales,wasmbind,oldtime,clock,winapi,serde + + rust_versions: + strategy: + matrix: + os: [ubuntu-latest] + rust_version: ["stable", "beta", "nightly"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} - override: true - - - uses: Swatinem/rust-cache@v1 - - name: Build and Test - run: bash ci/github.sh + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - run: cargo check --manifest-path bench/Cargo.toml --benches + - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets + # run --lib and --doc to avoid the long running integration tests + # which are run elsewhere + - run: cargo test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always + - run: cargo test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always + + features_check: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + - run: | + cargo hack check --feature-powerset --optional-deps arbitrary,serde \ + --skip __internal_bench,iana-time-zone,oldtime,pure-rust-locales,libc,winapi,rkyv-validation,wasmbind \ + --mutually-exclusive-features arbitrary,rkyv,rkyv-16,rkyv-32,rkyv-64,serde \ + --all-targets + # run using `bash` on all platforms for consistent + # line-continuation marks + shell: bash env: - RUST_VERSION: ${{ matrix.rust_version }} - EXHAUSTIVE_TZ: ${{ matrix.exhaustive_tz }} - CHECK_COMBINATORIC: ${{ matrix.check_combinatoric }} + RUSTFLAGS: "-D warnings" + - run: cargo test --no-default-features + - run: cargo test --no-default-features --features=alloc + - run: cargo test --no-default-features --features=unstable-locales + - run: cargo test --no-default-features --features=alloc,unstable-locales + - run: cargo test --no-default-features --features=now no_std: strategy: matrix: - os: [macos-latest, ubuntu-latest] - + os: [ubuntu-latest] + target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - - name: Install rust with no_std toolchain - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - target: thumbv6m-none-eabi - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build no_std lib - run: cargo build --target thumbv6m-none-eabi --color=always + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test - wasm: + alternative_targets: strategy: matrix: - os: [macos-latest] - + os: [ubuntu-latest] + target: + [ + wasm32-unknown-emscripten, + aarch64-apple-ios, + aarch64-linux-android, + ] runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - - uses: Swatinem/rust-cache@v1 + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - run: cargo build --target ${{ matrix.target }} --color=always - - name: Install node - uses: actions/setup-node@v1 + test_wasm: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable with: - node-version: '12' - - - name: Install wasm-pack - run: | - export RUST_BACKTRACE=1 - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - wasm-pack --version - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_simple + targets: wasm32-unknown-unknown + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-node@v7 + - uses: jetli/wasm-pack-action@v0.4.0 + # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment + # with the host system. + - run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind + + test_wasip1: + strategy: + matrix: + os: [ubuntu-latest] + target: [wasm32-wasip1] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + # We can't use `--all-features` because rkyv uses the mutually-exclusive-feature pattern + - run: cargo check --target ${{ matrix.target }} --all-targets --features=serde,unstable-locales cross-targets: strategy: matrix: target: - - x86_64-sun-solaris - + - x86_64-unknown-illumos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Install cross - run: bash ci/install-cross.sh + - uses: actions/checkout@v7 + - run: cargo install cross + - uses: Swatinem/rust-cache@v2 + - run: cross check --target ${{ matrix.target }} - - uses: Swatinem/rust-cache@v1 - - - name: Build static library - run: cross check --target ${{ matrix.target }} + cross-tests: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - run: cargo install cross + - uses: Swatinem/rust-cache@v2 + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always check-docs: runs-on: ubuntu-latest - name: Check doc links steps: - - uses: actions/checkout@v2 - - name: Install rust nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - - name: Check docs build without warnings + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: - RUSTDOCFLAGS: '-D warnings' - run: cargo +nightly doc + RUSTDOCFLAGS: "-D warnings --cfg docsrs" diff --git a/.gitignore b/.gitignore index a9d37c560c..f989181e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ target Cargo.lock +.tool-versions + +# for jetbrains users +.idea/ diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 9501a9d059..0000000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,41 +0,0 @@ -Chrono is mainly written by Kang Seonghoon , -and also the following people (in ascending order): - -Alex Mikhalev -Alexander Bulaev -Ashley Mannix -Ben Boeckel -Ben Eills -Brandon W Maister -Brandon W Maister -Cecile Tonglet -Colin Ray -Corey Farwell -Dan -Danilo Bargen -David Hewson -David Ross -David Tolnay -David Willie -Eric Findlay -Eunchong Yu -Frans Skarman -Huon Wilson -Igor Gnatenko -Jim Turner -Jisoo Park -Joe Wilm -John Heitmann -John Nagle -Jonas mg -János Illés -Ken Tossell -Martin Risell Lilja -Richard Petrie -Ryan Lewis -Sergey V. Shadoy -Sergey V. Galtsev -Steve Klabnik -Tom Gallacher -klutzy -kud1ing diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cfecebadd..c61a13f549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,9 @@ ChangeLog for Chrono ==================== -This documents all notable changes to [Chrono](https://github.com/chronotope/chrono). - -Chrono obeys the principle of [Semantic Versioning](http://semver.org/), with one caveat: we may -move previously-existing code behind a feature gate and put it behind a new feature. This new -feature will always be placed in the `previously-default` feature, which you can use to prevent -breakage if you use `no-default-features`. - -There were/are numerous minor versions before 1.0 due to the language changes. -Versions with only mechanical changes will be omitted from the following list. - -## 0.4.20 (unreleased) - -* Add more formatting documentation and examples. -* Add support for microseconds timestamps serde serialization/deserialization (#304) -* Fix `DurationRound` is not TZ aware (#495) -* Implement `DurationRound` for `NaiveDateTime` -* Add `DateTime::from_local()` to construct from given local date and time (#572) -* Correct build for wasm32-unknown-emscripten target (#568) -* Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) +This documents notable changes to [Chrono](https://github.com/chronotope/chrono) +up to and including version 0.4.19. For later releases, please review the +release notes on [GitHub](https://github.com/chronotope/chrono/releases). ## 0.4.19 @@ -159,7 +143,7 @@ Versions with only mechanical changes will be omitted from the following list. ### Fixes -* Make Datetime arithmatic adjust their offsets after discovering their new +* Make Datetime arithmetic adjust their offsets after discovering their new timestamps (@quodlibetor #337) * Put wasm-bindgen related code and dependencies behind a `wasmbind` feature gate. (@quodlibetor #335) @@ -254,7 +238,7 @@ Versions with only mechanical changes will be omitted from the following list. ### Features -* New `timestamp_millis` method on `DateTime` and `NaiveDateTim` that returns +* New `timestamp_millis` method on `DateTime` and `NaiveDateTime` that returns number of milliseconds since the epoch. (@quodlibetor) * Support exact decimal width on subsecond display for RFC3339 via a new `to_rfc3339_opts` method on `DateTime` (@dekellum) @@ -615,7 +599,7 @@ and replaced by 0.2.25 very shortly. Duh.) ### Added -- `Offset` is splitted into `TimeZone` (constructor) and `Offset` (storage) types. +- `Offset` is split into `TimeZone` (constructor) and `Offset` (storage) types. You would normally see only the former, as the latter is mostly an implementation detail. Most importantly, `Local` now can be used to directly construct timezone-aware values. @@ -677,7 +661,7 @@ and replaced by 0.2.25 very shortly. Duh.) so we simply let it go. In the case that `Time` is really required, one can use a simpler `NaiveTime`. - `NaiveTime` and `NaiveDate` can be freely combined and splitted, + `NaiveTime` and `NaiveDate` can be freely combined and split, and `TimeZone::from_{local,utc}_datetime` can be used to convert from/to the local time. - `with_offset` method has been removed. Use `with_timezone` method instead. @@ -745,4 +729,3 @@ and replaced by 0.2.25 very shortly. Duh.) ## 0.1.0 (2014-11-20) The initial version that was available to `crates.io`. - diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..b5bae89fbf --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,33 @@ +# Parser settings. +cff-version: 1.2.0 +message: Please cite this crate using these information. + +# Version information. +date-released: 2026-06-04 +version: 0.4.45 + +# Project information. +abstract: Date and time library for Rust +authors: + - alias: quodlibetor + family-names: Maister + given-names: Brandon W. + - alias: djc + family-names: Ochtman + given-names: Dirkjan + - alias: lifthrasiir + family-names: Seonghoon + given-names: Kang + - alias: esheppa + family-names: Sheppard + given-names: Eric + - alias: pitdicker + family-names: Dicker + given-names: Paul +license: + - Apache-2.0 + - MIT +repository-artifact: https://crates.io/crates/chrono +repository-code: https://github.com/chronotope/chrono +title: chrono +url: https://docs.rs/chrono diff --git a/Cargo.toml b/Cargo.toml index 6b4d48afa8..49604ae34d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,6 @@ [package] name = "chrono" -version = "0.4.19" -authors = [ - "Kang Seonghoon ", - "Brandon W Maister ", -] - +version = "0.4.45" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" @@ -13,66 +8,68 @@ repository = "https://github.com/chronotope/chrono" keywords = ["date", "time", "calendar"] categories = ["date-and-time"] readme = "README.md" -license = "MIT/Apache-2.0" -exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"] - -[badges] -travis-ci = { repository = "chronotope/chrono" } -appveyor = { repository = "chronotope/chrono" } +license = "MIT OR Apache-2.0" +include = ["src/*", "tests/*.rs", "LICENSE.txt", "CITATION.cff"] +edition = "2021" +rust-version = "1.62.0" [lib] name = "chrono" [features] -default = ["clock", "std", "oldtime"] +# Don't forget to adjust `ALL_NON_EXCLUSIVE_FEATURES` in CI scripts when adding a feature or an optional dependency. +default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] -std = [] -clock = ["libc", "std", "winapi"] -oldtime = ["time"] +defmt = ["dep:defmt", "pure-rust-locales?/defmt"] +libc = [] +winapi = ["windows-link"] +std = ["alloc"] +clock = ["winapi", "iana-time-zone", "now"] +now = ["std"] +core-error = [] +oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] -unstable-locales = ["pure-rust-locales", "alloc"] +unstable-locales = ["pure-rust-locales"] +# Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive. +rkyv = ["dep:rkyv", "rkyv/size_32"] +rkyv-16 = ["dep:rkyv", "rkyv?/size_16"] +rkyv-32 = ["dep:rkyv", "rkyv?/size_32"] +rkyv-64 = ["dep:rkyv", "rkyv?/size_64"] +rkyv-validation = ["rkyv?/validation"] +# Features for internal use only: __internal_bench = [] -__doctest = [] [dependencies] -libc = { version = "0.2.69", optional = true } -time = { version = "0.1.43", optional = true } -num-integer = { version = "0.1.36", default-features = false } num-traits = { version = "0.2", default-features = false } -rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.5.2", optional = true } +pure-rust-locales = { version = "0.8.2", optional = true } +rkyv = { version = "0.7.43", optional = true, default-features = false } +arbitrary = { version = "1.0.0", features = ["derive"], optional = true } +defmt = { version = "1.0.1", optional = true } -[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] +[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } -js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API +js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } +windows-link = { version = "0.2", optional = true } + +[target.'cfg(unix)'.dependencies] +iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } -bincode = { version = "0.8.0" } -num-iter = { version = "0.1.35", default-features = false } -criterion = { version = "0.3" } -doc-comment = { version = "0.3" } +similar-asserts = { version = "2.0.0" } +bincode = { version = "1.3.0" } +windows-bindgen = { version = "0.66" } # MSRV is 1.74 -[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] +[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" [package.metadata.docs.rs] -features = ["serde"] +features = ["arbitrary", "rkyv", "serde", "unstable-locales"] +rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] features = ["serde"] - -[[bench]] -name = "chrono" -required-features = ["__internal_bench"] -harness = false - -[[bench]] -name = "serde" -harness = false -required-features = ["serde"] diff --git a/LICENSE.txt b/LICENSE.txt index 924ff57f22..480fdf7e47 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ Rust-chrono is dual-licensed under The MIT License [1] and -Apache 2.0 License [2]. Copyright (c) 2014--2017, Kang Seonghoon and +Apache 2.0 License [2]. Copyright (c) 2014--2026, Kang Seonghoon and contributors. Nota Bene: This is same as the Rust Project's own license. diff --git a/Makefile b/Makefile deleted file mode 100644 index 46b39dd25f..0000000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -# this Makefile is mostly for the packaging convenience. -# casual users should use `cargo` to retrieve the appropriate version of Chrono. - -CHANNEL=stable - -.PHONY: all -all: - @echo 'Try `cargo build` instead.' - -.PHONY: authors -authors: - echo 'Chrono is mainly written by Kang Seonghoon ,' > AUTHORS.txt - echo 'and also the following people (in ascending order):' >> AUTHORS.txt - echo >> AUTHORS.txt - git log --format='%aN <%aE>' | grep -v 'Kang Seonghoon' | sort -u >> AUTHORS.txt - -.PHONY: readme README.md -readme: README.md - -README.md: src/lib.rs - ( ./ci/fix-readme.sh $< ) > $@ - -.PHONY: test -test: - CHANNEL=$(CHANNEL) ./ci/travis.sh - -.PHONY: doc -doc: authors readme - cargo doc --features 'serde rustc-serialize bincode' diff --git a/README.md b/README.md index 48af06cfdb..58e16f3c7f 100644 --- a/README.md +++ b/README.md @@ -1,419 +1,92 @@ -[Chrono][docsrs]: Date and Time for Rust +[Chrono][docsrs]: Timezone-aware date and time handling ======================================== [![Chrono GitHub Actions][gh-image]][gh-checks] [![Chrono on crates.io][cratesio-image]][cratesio] [![Chrono on docs.rs][docsrs-image]][docsrs] -[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] +[![Chat][discord-image]][discord] +[![codecov.io][codecov-img]][codecov-link] -[gh-image]: https://github.com/chronotope/chrono/workflows/test/badge.svg -[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest +[gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg?branch=main +[gh-checks]: https://github.com/chronotope/chrono/actions/workflows/test.yml?query=branch%3Amain [cratesio-image]: https://img.shields.io/crates/v/chrono.svg [cratesio]: https://crates.io/crates/chrono [docsrs-image]: https://docs.rs/chrono/badge.svg [docsrs]: https://docs.rs/chrono -[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg -[gitter]: https://gitter.im/chrono-rs/chrono +[discord-image]: https://img.shields.io/discord/976380008299917365?logo=discord +[discord]: https://discord.gg/sXpav4PS7M +[codecov-img]: https://img.shields.io/codecov/c/github/chronotope/chrono?logo=codecov +[codecov-link]: https://codecov.io/gh/chronotope/chrono -It aims to be a feature-complete superset of -the [time](https://github.com/rust-lang-deprecated/time) library. -In particular, +Chrono aims to provide all functionality needed to do correct operations on dates and times in the +[proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): -* Chrono strictly adheres to ISO 8601. -* Chrono is timezone-aware by default, with separate timezone-naive types. -* Chrono is space-optimal and (while not being the primary goal) reasonably efficient. +* The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware + by default, with separate timezone-naive types. +* Operations that may produce an invalid or ambiguous date and time return `Option` or + [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). +* Configurable parsing and formatting with an `strftime` inspired date and time formatting syntax. +* The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with + the current timezone of the OS. +* Types and operations are implemented to be reasonably efficient. -There were several previous attempts to bring a good date and time library to Rust, -which Chrono builds upon and should acknowledge: +Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate +[Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for +full timezone support. -* [Initial research on - the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) +## Documentation -Any significant changes to Chrono are documented in -the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. +See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference. +## Limitations -## Usage - -Put this in your `Cargo.toml`: - -```toml -[dependencies] -chrono = "0.4" -``` - -### Features +* Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +* Date types are limited to about +/- 262,000 years from the common epoch. +* Time types are limited to nanosecond accuracy. +* Leap seconds can be represented, but Chrono does not fully support them. + See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). -Chrono supports various runtime environments and operating systems, and has -several features that may be enabled or disabled. +## Crate features Default features: -- `alloc`: Enable features that depend on allocation (primarily string formatting) -- `std`: Enables functionality that depends on the standard library. This - is a superset of `alloc` and adds interoperation with standard library types - and traits. -- `clock`: enables reading the system time (`now`), independent of whether - `std::time::SystemTime` is present, depends on having a libc. +* `alloc`: Enable features that depend on allocation (primarily string formatting). +* `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` + and adds interoperation with standard library types and traits. +* `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +* `now`: Enables reading the system time (`now`). +* `wasmbind`: Interface with the JS Date API for the `wasm32` target. Optional features: -- `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project -- [`serde`][]: Enable serialization/deserialization via serde. -- `unstable-locales`: Enable localization. This adds various methods with a - `_localized` suffix. The implementation and API may change or even be - removed in a patch release. Feedback welcome. - -[`serde`]: https://github.com/serde-rs/serde -[wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen - -See the [cargo docs][] for examples of specifying features. - -[cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features - -## Overview - -### Duration - -Chrono currently uses its own [`Duration`] type to represent the magnitude -of a time span. Since this has the same name as the newer, standard type for -duration, the reference will refer this type as `OldDuration`. - -Note that this is an "accurate" duration represented as seconds and -nanoseconds and does not represent "nominal" components such as days or -months. - -When the `oldtime` feature is enabled, [`Duration`] is an alias for the -[`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -type from v0.1 of the time crate. time v0.1 is deprecated, so new code -should disable the `oldtime` feature and use the `chrono::Duration` type -instead. The `oldtime` feature is enabled by default for backwards -compatibility, but future versions of Chrono are likely to remove the -feature entirely. - -Chrono does not yet natively support -the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -but it will be supported in the future. -Meanwhile you can convert between two types with -[`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -and -[`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) -methods. - -### Date and Time - -Chrono provides a -[**`DateTime`**](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) -type to represent a date and a time in a timezone. - -For more abstract moment-in-time tracking such as internal timekeeping -that is unconcerned with timezones, consider -[`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -which tracks your system clock, or -[`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -is an opaque but monotonically-increasing representation of a moment in time. - -`DateTime` is timezone-aware and must be constructed from -the [**`TimeZone`**](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html) object, -which defines how the local date is converted to and back from the UTC date. -There are three well-known `TimeZone` implementations: - -* [**`Utc`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. - -* [**`Local`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html) specifies the system local time zone. - -* [**`FixedOffset`**](https://docs.rs/chrono/0.4/chrono/offset/struct.FixedOffset.html) specifies - an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. - This often results from the parsed textual date and time. - Since it stores the most information and does not depend on the system environment, - you would want to normalize other `TimeZone`s into this type. - -`DateTime`s with different `TimeZone` types are distinct and do not mix, -but can be converted to each other using -the [`DateTime::with_timezone`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.with_timezone) method. - -You can get the current date and time in the UTC time zone -([`Utc::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html#method.now)) -or in the local time zone -([`Local::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html#method.now)). - -```rust -use chrono::prelude::*; - -let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` -let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -``` - -Alternatively, you can create your own date and time. -This is a bit verbose due to Rust's lack of function and method overloading, -but in turn we get a rich combination of initialization methods. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` -// July 8 is 188th day of the year 2014 (`o` for "ordinal") -assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); -// July 8 is Tuesday in ISO week 28 of the year 2014. -assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); - -let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); - -// dynamic verification -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), - LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); - -// other time zone objects can be used to construct a local datetime. -// obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); -assert_eq!(dt, fixed_dt); -``` - -Various properties are available to the date and time, and can be altered individually. -Most of them are defined in the traits [`Datelike`](https://docs.rs/chrono/0.4/chrono/trait.Datelike.html) and -[`Timelike`](https://docs.rs/chrono/0.4/chrono/trait.Timelike.html) which you should `use` before. -Addition and subtraction is also supported. -The following illustrates most supported operations to the date and time: - -```rust - -use chrono::prelude::*; -use chrono::Duration; - -// assume this returned `2014-11-28T21:45:59.324310806+09:00`: -let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); - -// property accessors -assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); -assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls -assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59)); -assert_eq!(dt.weekday(), Weekday::Fri); -assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7 -assert_eq!(dt.ordinal(), 332); // the day of year -assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1 - -// time zone accessor and manipulation -assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); - -// a sample of property manipulations (validates dynamically) -assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday -assert_eq!(dt.with_day(32), None); -assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE - -// arithmetic operations -let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); -assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), - Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), - Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -``` - -### Formatting and Parsing +* `serde`: Enable serialization/deserialization via [serde]. +* `rkyv`: Deprecated, use the `rkyv-*` features. +* `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types. +* `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types. +* `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types. +* `rkyv-validation`: Enable rkyv validation support using `bytecheck`. +* `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. +* `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. + The implementation and API may change or even be removed in a patch release. Feedback welcome. +* `oldtime`: This feature no longer has any effect; it used to offer compatibility with the `time` 0.1 crate. -Formatting is done via the [`format`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.format) method, -which format is equivalent to the familiar `strftime` format. +Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. -See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -The default `to_string` method and `{:?}` specifier also give a reasonable representation. -Chrono also provides [`to_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc2822) and -[`to_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc3339) methods -for well-known formats. - -Chrono now also provides date formatting in almost any language without the -help of an additional C library. This functionality is under the feature -`unstable-locales`: - -```toml -chrono = { version = "0.4", features = ["unstable-locales"] } -``` - -The `unstable-locales` feature requires and implies at least the `alloc` feature. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); - -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); -assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); -assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000"); -assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00"); -assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); - -// Note that milli/nanoseconds are only printed if they are non-zero -let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); -assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); -``` - -Parsing can be done with three methods: - -1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait - (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method - on a string) can be used for parsing `DateTime`, `DateTime` and - `DateTime` values. This parses what the `{:?}` - ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) - format specifier prints, and requires the offset to be present. - -2. [`DateTime::parse_from_str`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_str) parses - a date and time with offsets and returns `DateTime`. - This should be used when the offset is a part of input and the caller cannot guess that. - It *cannot* be used when the offset can be missing. - [`DateTime::parse_from_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc2822) - and - [`DateTime::parse_from_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc3339) - are similar but for well-known formats. - -3. [`Offset::datetime_from_str`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.datetime_from_str) is - similar but returns `DateTime` of given offset. - When the explicit offset is missing from the input, it simply uses given offset. - It issues an error when the input contains an explicit offset different - from the current offset. - -More detailed control over the parsing process is available via -[`format`](https://docs.rs/chrono/0.4/chrono/format/index.html) module. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); - -// method 1 -assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); - -// method 2 -assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); - -// method 3 -assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); - -// oops, the year is missing! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); -// oops, the format string does not include the year at all! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); -// oops, the weekday is incorrect! -assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); -``` - -Again : See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -### Conversion from and to EPOCH timestamps - -Use [`Utc.timestamp(seconds, nanoseconds)`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.timestamp) -to construct a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) from a UNIX timestamp -(seconds, nanoseconds that passed since January 1st 1970). - -Use [`DateTime.timestamp`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -from a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html). Additionally, you can use -[`DateTime.timestamp_subsec_nanos`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp_subsec_nanos) -to get the number of additional number of nanoseconds. - -```rust -// We need the trait in scope to use Utc::timestamp(). -use chrono::{DateTime, TimeZone, Utc}; - -// Construct a datetime from epoch: -let dt = Utc.timestamp(1_500_000_000, 0); -assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); - -// Get epoch value from a datetime: -let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); -assert_eq!(dt.timestamp(), 1_500_000_000); -``` - -### Individual date - -Chrono also provides an individual date type ([**`Date`**](https://docs.rs/chrono/0.4/chrono/struct.Date.html)). -It also has time zones attached, and have to be constructed via time zones. -Most operations available to `DateTime` are also available to `Date` whenever appropriate. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -assert_eq!(Utc::today(), Utc::now().date()); -assert_eq!(Local::today(), Local::now().date()); - -assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); -assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), - "070809"); -``` - -There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. - -`DateTime` has [`date`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.date) method -which returns a `Date` which represents its date component. -There is also a [`time`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.time) method, -which simply returns a naive local time described below. - -### Naive date and time - -Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -as [**`NaiveDate`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html), -[**`NaiveTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html) and -[**`NaiveDateTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDateTime.html) respectively. - -They have almost equivalent interfaces as their timezone-aware twins, -but are not associated to time zones obviously and can be quite low-level. -They are mostly useful for building blocks for higher-level types. - -Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -[`naive_local`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_local) returns -a view to the naive local time, -and [`naive_utc`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_utc) returns -a view to the naive UTC time. - -## Limitations +[serde]: https://github.com/serde-rs/serde +[rkyv]: https://github.com/rkyv/rkyv -Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. +## Rust version requirements -Date types are limited in about +/- 262,000 years from the common epoch. -Time types are limited in the nanosecond accuracy. +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.62.0**. -[Leap seconds are supported in the representation but -Chrono doesn't try to make use of them](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html#leap-second-handling). -(The main reason is that leap seconds are not really predictable.) -Almost *every* operation over the possible leap seconds will ignore them. -Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -if you want. +The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done +lightly. -Chrono inherently does not support an inaccurate or partial date and time representation. -Any operation that can be ambiguous will return `None` in such cases. -For example, "a month later" of 2014-01-30 is not well-defined -and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. +## License -Non ISO week handling is not yet supported. -For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -crate ([sources](https://github.com/bcourtine/chrono-ext/)). +This project is licensed under either of -Advanced time zone handling is not yet supported. -For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. +* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +* [MIT License](https://opensource.org/licenses/MIT) +at your option. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6057e851e2..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,12 +0,0 @@ -# TODO: delete this without breaking all PRs -environment: - matrix: - - TARGET: nightly-i686-pc-windows-gnu -matrix: - allow_failures: - - channel: nightly - -build: false - -test_script: - - echo "stub" diff --git a/bench/Cargo.toml b/bench/Cargo.toml new file mode 100644 index 0000000000..ff0f95bc28 --- /dev/null +++ b/bench/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "benches" +version = "0.1.0" +edition = "2021" +publish = false + +# Even as a `dev-dependency` Criterion and its dependencies can affect the MSRV of chrono. +# But not when it lives in a separate crate :-). +# See https://github.com/chronotope/chrono/pull/1104. + +[features] +unstable-locales = ["chrono/unstable-locales"] + +[dependencies] +chrono = { path = "..", features = ["__internal_bench", "serde"] } + +[[bench]] +name = "chrono" +harness = false + +[[bench]] +name = "serde" +harness = false + +[dev-dependencies] +criterion = "0.5.0" +serde_json = "1" diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs new file mode 100644 index 0000000000..b2b96715a8 --- /dev/null +++ b/bench/benches/chrono.rs @@ -0,0 +1,270 @@ +//! Benchmarks for chrono that just depend on std + +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; + +#[cfg(feature = "unstable-locales")] +use chrono::Locale; +use chrono::format::StrftimeItems; +use chrono::prelude::*; +use chrono::{__BenchYearFlags, DateTime, FixedOffset, Local, TimeDelta, Utc}; + +fn bench_date_from_ymd(c: &mut Criterion) { + c.bench_function("bench_date_from_ymd", |b| { + let expected = NaiveDate::from_ymd_opt(2024, 2, 12); + b.iter(|| { + let (y, m, d) = black_box((2024, 2, 12)); + assert_eq!(NaiveDate::from_ymd_opt(y, m, d), expected) + }) + }); +} + +fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { + c.bench_function("bench_datetime_parse_from_rfc2822", |b| { + b.iter(|| { + let str = black_box("Wed, 18 Feb 2015 23:16:09 +0000"); + DateTime::parse_from_rfc2822(str).unwrap() + }) + }); +} + +fn bench_datetime_parse_from_rfc3339(c: &mut Criterion) { + c.bench_function("bench_datetime_parse_from_rfc3339", |b| { + b.iter(|| { + let str = black_box("2015-02-18T23:59:60.234567+05:00"); + DateTime::parse_from_rfc3339(str).unwrap() + }) + }); +} + +fn bench_datetime_from_str(c: &mut Criterion) { + c.bench_function("bench_datetime_from_str", |b| { + b.iter(|| { + use std::str::FromStr; + let str = black_box("2019-03-30T18:46:57.193Z"); + DateTime::::from_str(str).unwrap() + }) + }); +} + +fn bench_datetime_to_rfc2822(c: &mut Criterion) { + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); +} + +fn bench_datetime_to_rfc3339(c: &mut Criterion) { + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); +} + +fn bench_datetime_to_rfc3339_opts(c: &mut Criterion) { + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + c.bench_function("bench_datetime_to_rfc3339_opts", |b| { + b.iter(|| black_box(dt).to_rfc3339_opts(SecondsFormat::Nanos, true)) + }); +} + +fn bench_year_flags_from_year(c: &mut Criterion) { + c.bench_function("bench_year_flags_from_year", |b| { + b.iter(|| { + for year in -999i32..1000 { + let _ = __BenchYearFlags::from_year(black_box(year)); + } + }) + }); +} + +fn bench_get_local_time(c: &mut Criterion) { + c.bench_function("bench_get_local_time", |b| { + b.iter(|| { + let _ = Local::now(); + }) + }); +} + +/// Returns the number of multiples of `div` in the range `start..end`. +/// +/// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the +/// behaviour is defined by the following equation: +/// `in_between(start, end, div) == - in_between(end, start, div)`. +/// +/// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. +/// +/// # Panics +/// +/// Panics if `div` is not positive. +fn in_between(start: i32, end: i32, div: i32) -> i32 { + assert!(div > 0, "in_between: nonpositive div = {div}"); + let start = (start.div_euclid(div), start.rem_euclid(div)); + let end = (end.div_euclid(div), end.rem_euclid(div)); + // The lowest multiple of `div` greater than or equal to `start`, divided. + let start = start.0 + (start.1 != 0) as i32; + // The lowest multiple of `div` greater than or equal to `end`, divided. + let end = end.0 + (end.1 != 0) as i32; + end - start +} + +/// Alternative implementation to `Datelike::num_days_from_ce` +fn num_days_from_ce_alt(date: &Date) -> i32 { + let year = date.year(); + let diff = move |div| in_between(1, year, div); + // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all + // the multiples of 4 except multiples of 100 but including multiples of 400. + date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) +} + +fn bench_num_days_from_ce(c: &mut Criterion) { + let mut group = c.benchmark_group("num_days_from_ce"); + for year in &[1, 500, 2000, 2019] { + let d = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap(); + group.bench_with_input(BenchmarkId::new("new", year), &d, |b, y| { + b.iter(|| num_days_from_ce_alt(y)) + }); + group.bench_with_input(BenchmarkId::new("classic", year), &d, |b, y| { + b.iter(|| y.num_days_from_ce()) + }); + } +} + +fn bench_parse_strftime(c: &mut Criterion) { + c.bench_function("bench_parse_strftime", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new(str); + black_box(items.collect::>()); + }) + }); +} + +#[cfg(feature = "unstable-locales")] +fn bench_parse_strftime_localized(c: &mut Criterion) { + c.bench_function("bench_parse_strftime_localized", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new_with_locale(str, Locale::nl_NL); + black_box(items.collect::>()); + }) + }); +} + +fn bench_format(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format", |b| { + b.iter(|| format!("{}", black_box(dt).format("%Y-%m-%dT%H:%M:%S%.f%:z"))) + }); +} + +fn bench_format_with_items(c: &mut Criterion) { + let dt = Local::now(); + let items: Vec<_> = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%.f%:z").collect(); + c.bench_function("bench_format_with_items", |b| { + b.iter(|| format!("{}", black_box(dt).format_with_items(items.iter()))) + }); +} + +fn benches_delayed_format(c: &mut Criterion) { + let mut group = c.benchmark_group("delayed_format"); + let dt = Local::now(); + group.bench_function(BenchmarkId::new("with_display", dt), |b| { + b.iter_batched( + || dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), + |df| black_box(df).to_string(), + criterion::BatchSize::SmallInput, + ) + }); + group.bench_function(BenchmarkId::new("with_string_buffer", dt), |b| { + b.iter_batched( + || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), + |(df, string)| black_box(df).write_to(&mut black_box(string)), + criterion::BatchSize::SmallInput, + ) + }); +} + +fn bench_format_manual(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format_manual", |b| { + b.iter(|| { + black_box(dt); + format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}{:+02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second(), + dt.nanosecond(), + dt.offset().fix().local_minus_utc() / 3600, + dt.offset().fix().local_minus_utc() / 60, + ) + }) + }); +} + +fn bench_naivedate_add_signed(c: &mut Criterion) { + let date = NaiveDate::from_ymd_opt(2023, 7, 29).unwrap(); + let extra = TimeDelta::try_days(25).unwrap(); + c.bench_function("bench_naivedate_add_signed", |b| { + b.iter(|| black_box(date).checked_add_signed(extra).unwrap()) + }); +} + +fn bench_datetime_with(c: &mut Criterion) { + let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2023, 9, 23, 7, 36, 0).unwrap(); + c.bench_function("bench_datetime_with", |b| { + b.iter(|| black_box(black_box(dt).with_hour(12)).unwrap()) + }); +} + +criterion_group!( + benches, + bench_date_from_ymd, + bench_datetime_parse_from_rfc2822, + bench_datetime_parse_from_rfc3339, + bench_datetime_from_str, + bench_datetime_to_rfc2822, + bench_datetime_to_rfc3339, + bench_datetime_to_rfc3339_opts, + bench_year_flags_from_year, + bench_num_days_from_ce, + bench_get_local_time, + bench_parse_strftime, + bench_format, + bench_format_with_items, + bench_format_manual, + benches_delayed_format, + bench_naivedate_add_signed, + bench_datetime_with, +); + +#[cfg(feature = "unstable-locales")] +criterion_group!(unstable_locales, bench_parse_strftime_localized,); + +#[cfg(not(feature = "unstable-locales"))] +criterion_main!(benches); +#[cfg(feature = "unstable-locales")] +criterion_main!(benches, unstable_locales); diff --git a/benches/serde.rs b/bench/benches/serde.rs similarity index 87% rename from benches/serde.rs rename to bench/benches/serde.rs index 860b06e1ae..8791019f60 100644 --- a/benches/serde.rs +++ b/bench/benches/serde.rs @@ -1,7 +1,4 @@ -extern crate chrono; -extern crate criterion; - -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use chrono::NaiveDateTime; diff --git a/bench/src/lib.rs b/bench/src/lib.rs new file mode 100644 index 0000000000..d87371caaa --- /dev/null +++ b/bench/src/lib.rs @@ -0,0 +1 @@ +// This file only exists to make `benches` a valid crate. diff --git a/benches/chrono.rs b/benches/chrono.rs deleted file mode 100644 index 1c640634ac..0000000000 --- a/benches/chrono.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Benchmarks for chrono that just depend on std - -extern crate chrono; -extern crate criterion; - -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; - -use chrono::prelude::*; -use chrono::{DateTime, FixedOffset, Utc, __BenchYearFlags}; - -fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { - c.bench_function("bench_datetime_parse_from_rfc2822", |b| { - b.iter(|| { - let str = black_box("Wed, 18 Feb 2015 23:16:09 +0000"); - DateTime::parse_from_rfc2822(str).unwrap() - }) - }); -} - -fn bench_datetime_parse_from_rfc3339(c: &mut Criterion) { - c.bench_function("bench_datetime_parse_from_rfc3339", |b| { - b.iter(|| { - let str = black_box("2015-02-18T23:59:60.234567+05:00"); - DateTime::parse_from_rfc3339(str).unwrap() - }) - }); -} - -fn bench_datetime_from_str(c: &mut Criterion) { - c.bench_function("bench_datetime_from_str", |b| { - b.iter(|| { - use std::str::FromStr; - let str = black_box("2019-03-30T18:46:57.193Z"); - DateTime::::from_str(str).unwrap() - }) - }); -} - -fn bench_datetime_to_rfc2822(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); - c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); -} - -fn bench_datetime_to_rfc3339(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); - c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); -} - -fn bench_year_flags_from_year(c: &mut Criterion) { - c.bench_function("bench_year_flags_from_year", |b| { - b.iter(|| { - for year in -999i32..1000 { - __BenchYearFlags::from_year(year); - } - }) - }); -} - -/// Returns the number of multiples of `div` in the range `start..end`. -/// -/// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the -/// behaviour is defined by the following equation: -/// `in_between(start, end, div) == - in_between(end, start, div)`. -/// -/// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. -/// -/// # Panics -/// -/// Panics if `div` is not positive. -fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); - let start = (start.div_euclid(div), start.rem_euclid(div)); - let end = (end.div_euclid(div), end.rem_euclid(div)); - // The lowest multiple of `div` greater than or equal to `start`, divided. - let start = start.0 + (start.1 != 0) as i32; - // The lowest multiple of `div` greater than or equal to `end`, divided. - let end = end.0 + (end.1 != 0) as i32; - end - start -} - -/// Alternative implementation to `Datelike::num_days_from_ce` -fn num_days_from_ce_alt(date: &Date) -> i32 { - let year = date.year(); - let diff = move |div| in_between(1, year, div); - // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all - // the multiples of 4 except multiples of 100 but including multiples of 400. - date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) -} - -fn bench_num_days_from_ce(c: &mut Criterion) { - let mut group = c.benchmark_group("num_days_from_ce"); - for year in &[1, 500, 2000, 2019] { - let d = NaiveDate::from_ymd(*year, 1, 1); - group.bench_with_input(BenchmarkId::new("new", year), &d, |b, y| { - b.iter(|| num_days_from_ce_alt(y)) - }); - group.bench_with_input(BenchmarkId::new("classic", year), &d, |b, y| { - b.iter(|| y.num_days_from_ce()) - }); - } -} - -criterion_group!( - benches, - bench_datetime_parse_from_rfc2822, - bench_datetime_parse_from_rfc3339, - bench_datetime_from_str, - bench_datetime_to_rfc2822, - bench_datetime_to_rfc3339, - bench_year_flags_from_year, - bench_num_days_from_ce, -); - -criterion_main!(benches); diff --git a/ci/_shlib.sh b/ci/_shlib.sh deleted file mode 100644 index 74f5c383b1..0000000000 --- a/ci/_shlib.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -# Use github workflow commands to make sections of the tests more obvious -# https://help.github.com/en/actions/reference/workflow-commands-for-github-actions - - -# Run a test as a "group" -- output will be folded and hidden by default -runt() { - echo "::group::$*" - # stdout is occasionally swallowed by gh actions - "$@" >&2 - echo "::endgroup::$*" -} - -runv() { - echo "🚀>$ $*" >&2 - # stdout is occasionally swallowed by gh actions - "$@" >&2 -} diff --git a/ci/core-test/Cargo.toml b/ci/core-test/Cargo.toml index b35ff9a7dc..4258f9d90c 100644 --- a/ci/core-test/Cargo.toml +++ b/ci/core-test/Cargo.toml @@ -2,8 +2,8 @@ name = "core-test" version = "0.1.0" authors = [ - "Kang Seonghoon ", - "Brandon W Maister ", + "Kang Seonghoon ", + "Brandon W Maister ", ] edition = "2018" @@ -11,4 +11,4 @@ edition = "2018" chrono = { path = "../..", default-features = false, features = ["serde"] } [features] -alloc = ["chrono/alloc"] \ No newline at end of file +alloc = ["chrono/alloc"] diff --git a/ci/core-test/src/lib.rs b/ci/core-test/src/lib.rs index e311edb5bc..4af7d2ecc5 100644 --- a/ci/core-test/src/lib.rs +++ b/ci/core-test/src/lib.rs @@ -3,5 +3,5 @@ use chrono::{TimeZone, Utc}; pub fn create_time() { - let _ = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0); + let _ = Utc.with_ymd_and_hms(2019, 1, 1, 0, 0, 0).unwrap(); } diff --git a/ci/fix-readme.sh b/ci/fix-readme.sh deleted file mode 100755 index c08118f12c..0000000000 --- a/ci/fix-readme.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -VERSION="$( cargo read-manifest | python -c 'import json, sys; print(json.load(sys.stdin)["version"][:3])')" -LIB="$1" - -# Make the Chrono in the header a link to the docs -awk '/^\/\/! # Chrono: / { print "[Chrono][docsrs]:", substr($0, index($0, $4))}' "$LIB" -awk '/^\/\/! # Chrono: / { print "[Chrono][docsrs]:", substr($0, index($0, $4))}' "$LIB" | sed 's/./=/g' -# Add all the badges -echo ' -[![Chrono GitHub Actions][gh-image]][gh-checks] -[![Chrono on crates.io][cratesio-image]][cratesio] -[![Chrono on docs.rs][docsrs-image]][docsrs] -[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] - -[gh-image]: https://github.com/chronotope/chrono/workflows/test/badge.svg -[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest -[cratesio-image]: https://img.shields.io/crates/v/chrono.svg -[cratesio]: https://crates.io/crates/chrono -[docsrs-image]: https://docs.rs/chrono/badge.svg -[docsrs]: https://docs.rs/chrono -[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg -[gitter]: https://gitter.im/chrono-rs/chrono' - -# print the section between the header and the usage -awk '/^\/\/! # Chrono:/,/^\/\/! ## /' "$LIB" | cut -b 5- | grep -v '^#' | \ - sed 's/](\.\//](https:\/\/docs.rs\/chrono\/'"$VERSION"'\/chrono\//g' -echo -# Replace relative doc links with links to this exact version of docs on -# docs.rs -awk '/^\/\/! ## /,!/^\/\/!/' "$LIB" | cut -b 5- | grep -v '^# ' | \ - sed 's/](\.\//](https:\/\/docs.rs\/chrono\/'"$VERSION"'\/chrono\//g' \ diff --git a/ci/github.sh b/ci/github.sh deleted file mode 100755 index ee62d46fb1..0000000000 --- a/ci/github.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# shellcheck source=ci/_shlib.sh -source "${BASH_SOURCE[0]%/*}/_shlib.sh" - -TEST_TZS=(ACST-9:30 EST4 UTC0 Asia/Katmandu) -FEATURES=(std serde clock "alloc serde" unstable-locales) -CHECK_FEATURES=(alloc "std unstable-locales" "serde clock" "clock unstable-locales") -RUST_113_FEATURES=(rustc-serialize serde) - -main() { - if [[ "$*" =~ "-h" ]]; then - echo -n "usage: ENV_VARS... $0 - -Recognized environment variables. Their values are as they are so that they are -meaningful in the github actions feature matrix UI. - - RUST_VERSION The rust version currently being tested - This doesn't set the version, it is just used to test - WASM Empty or 'yes_wasm' - CORE 'std' or 'no_std' - EXHAUSTIVE_TZ Emptly or 'all_tzs' - CHECK_COMBINATORIC Combine various features and verify that we compile -" - exit - fi - - runv cargo --version - - if [[ ${RUST_VERSION:-} != 1.13.0 ]]; then - if [[ ${WASM:-} == yes_wasm ]]; then - test_wasm - elif [[ ${WASM:-} == wasm_simple ]]; then - test_wasm_simple - elif [[ ${CORE:-} == no_std ]]; then - test_core - elif [[ ${EXHAUSTIVE_TZ:-} == all_tzs ]]; then - test_all_tzs - elif [[ ${CHECK_COMBINATORIC:-} == combinatoric ]]; then - check_combinatoric - else - test_regular UTC0 - fi - elif [[ ${RUST_VERSION:-} == 1.13.0 ]]; then - test_113 - else - echo "ERROR: didn't run any tests" - exit 1 - fi -} - -test_all_tzs() { - for tz in "${TEST_TZS[@]}"; do - test_regular "$tz" - done -} - -test_regular() { - tz="$1" && shift - - runt env TZ="$tz" cargo test --features __doctest,unstable-locales --color=always -- --color=always - for feature in "${FEATURES[@]}"; do - runt env TZ="$tz" cargo test --no-default-features --features "$feature" --lib --color=always -- --color=always - done -} - -check_combinatoric() { - runt cargo check --no-default-features - runt cargo check --all-features - for feature in "${CHECK_FEATURES[@]}"; do - runt cargo check --no-default-features --features "$feature" --lib --color=always - done -} - -test_113() { - runv cargo build --color=always - for feature in "${RUST_113_FEATURES[@]}"; do - runt cargo build --features "$feature" --color=always - done -} - -test_core() { - ( - cd ci/core-test - runt cargo build --target thumbv6m-none-eabi --color=always - ) -} - -test_wasm() { - if ! command -v node; then - echo "node is not installed, can't run wasm-pack tests" - exit 1 - fi - if ! command -v wasm-pack >/dev/null; then - echo "::group::curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh" - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - runv wasm-pack --version - fi - test_wasm_simple -} - -test_wasm_simple() { - if ! runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind ; then - # sometimes on github the initial build takes 8-10 minutes, and we - # check that the time makes sense inside the test by approximately - # comparing it to the env var, - # - # so re-run the test in case it took too long - runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind - fi -} - -main "$@" diff --git a/ci/install-cross.sh b/ci/install-cross.sh deleted file mode 100644 index b5786f1f60..0000000000 --- a/ci/install-cross.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -euxo pipefail - -main() { - local target= - if [[ "$(os)" == "ubuntu-latest" || "$(os)" == Linux ]]; then - target=x86_64-unknown-linux-musl - # shellcheck disable=SC2209 - sort=sort - else - target=x86_64-apple-darwin - sort=gsort - fi - - # This fetches latest stable release - local tag - tag=$(git ls-remote --tags --refs --exit-code https://github.com/rust-embedded/cross \ - | cut -d/ -f3 \ - | grep -E '^v[0.1.0-9.]+$' \ - | $sort --version-sort \ - | tail -n1) - - curl -LSfs https://japaric.github.io/trust/install.sh | \ - sh -s -- \ - --force \ - --git rust-embedded/cross \ - --tag "$tag" \ - --target $target - - cross --version -} - -# get an os name, either github's pre-set one or the result of uname -os() { - if [ -n "${OS_NAME:-}" ]; then - echo "$OS_NAME" - return - fi - uname -} - -main diff --git a/ci/lint.sh b/ci/lint.sh deleted file mode 100755 index 360d72cd22..0000000000 --- a/ci/lint.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# shellcheck source=ci/_shlib.sh -source "${BASH_SOURCE[0]%/*}/_shlib.sh" - -main() { - # rustflags are present because of: https://github.com/rust-lang/rust-clippy/issues/5749 - runt env RUSTFLAGS="-Dwarnings" cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive - runt cargo fmt -- --check --color=always - runt make readme - runv git diff --exit-code -- README.md -} - -main diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000000..edca3624ba --- /dev/null +++ b/deny.toml @@ -0,0 +1,12 @@ +[licenses] +allow = ["Apache-2.0", "MIT", "Unicode-3.0"] +private = { ignore = true } + +[advisories] +yanked = "deny" +ignore = [ + # bincode is deprecated -- just a dev-dependency in this crate + "RUSTSEC-2025-0141", + # proc-macro-error2 is unmaintained, via defmt + "RUSTSEC-2026-0173", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bcbc3aad25..20dae08e95 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" +libfuzzer-sys = "0.4" [dependencies.chrono] path = ".." @@ -22,3 +22,7 @@ members = ["."] [[bin]] name = "fuzz_reader" path = "fuzz_targets/fuzz_reader.rs" + +[[bin]] +name = "fuzz_format" +path = "fuzz_targets/fuzz_format.rs" diff --git a/fuzz/fuzz_targets/fuzz_format.rs b/fuzz/fuzz_targets/fuzz_format.rs new file mode 100644 index 0000000000..6a7d395e29 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_format.rs @@ -0,0 +1,7 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: (String, String)| { + use chrono::prelude::*; + let _ = DateTime::parse_from_str(&data.0, &data.1); +}); diff --git a/rustfmt.toml b/rustfmt.toml index 2a35f0230c..d6b90a3179 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ +style_edition = "2024" use_small_heuristics = "Max" diff --git a/src/date.rs b/src/date.rs index a12f383a18..9356dd052b 100644 --- a/src/date.rs +++ b/src/date.rs @@ -2,22 +2,24 @@ // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date with time zone. +#![allow(deprecated)] -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash}; -use oldtime::Duration as OldDuration; -#[cfg(feature = "unstable-locales")] -use format::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::{DelayedFormat, Item, StrftimeItems}; -use naive::{self, IsoWeek, NaiveDate, NaiveTime}; -use offset::{TimeZone, Utc}; -use DateTime; -use {Datelike, Weekday}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use crate::format::Locale; +#[cfg(feature = "alloc")] +use crate::format::{DelayedFormat, Item, StrftimeItems}; +use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::{TimeZone, Utc}; +use crate::{DateTime, Datelike, TimeDelta, Weekday}; /// ISO 8601 calendar date with time zone. /// @@ -36,7 +38,7 @@ use {Datelike, Weekday}; /// the corresponding local date should exist for at least a moment. /// (It may still have a gap from the offset changes.) /// -/// - The `TimeZone` is free to assign *any* [`Offset`](::offset::Offset) to the +/// - The `TimeZone` is free to assign *any* [`Offset`](crate::offset::Offset) to the /// local date, as long as that offset did occur in given day. /// /// For example, if `2015-03-08T01:59-08:00` is followed by `2015-03-08T03:00-07:00`, @@ -45,37 +47,43 @@ use {Datelike, Weekday}; /// /// - Once constructed as a full `DateTime`, [`DateTime::date`] and other associated /// methods should return those for the original `Date`. For example, if `dt = -/// tz.ymd(y,m,d).hms(h,n,s)` were valid, `dt.date() == tz.ymd(y,m,d)`. +/// tz.ymd_opt(y,m,d).unwrap().hms(h,n,s)` were valid, `dt.date() == tz.ymd_opt(y,m,d).unwrap()`. /// /// - The date is timezone-agnostic up to one day (i.e. practically always), /// so the local date and UTC date should be equal for most cases -/// even though the raw calculation between `NaiveDate` and `Duration` may not. +/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not. +#[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime` instead")] #[derive(Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Date { date: NaiveDate, offset: Tz::Offset, } /// The minimum possible `Date`. -pub const MIN_DATE: Date = Date { date: naive::MIN_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")] +pub const MIN_DATE: Date = Date::::MIN_UTC; /// The maximum possible `Date`. -pub const MAX_DATE: Date = Date { date: naive::MAX_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] +pub const MAX_DATE: Date = Date::::MAX_UTC; impl Date { /// Makes a new `Date` with given *UTC* date and offset. /// The local date should be constructed via the `TimeZone` trait. - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] + #[must_use] pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date { - Date { date: date, offset: offset } + Date { date, offset } } /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// - /// Panics on invalid datetime. + /// Returns `None` on invalid datetime. #[inline] + #[must_use] pub fn and_time(&self, time: NaiveTime) -> Option> { let localdt = self.naive_local().and_time(time); self.timezone().from_local_datetime(&localdt).single() @@ -85,7 +93,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")] #[inline] + #[must_use] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") } @@ -95,6 +105,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute and/or second. #[inline] + #[must_use] pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option> { NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time)) } @@ -104,7 +115,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")] #[inline] + #[must_use] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } @@ -115,6 +128,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or millisecond. #[inline] + #[must_use] pub fn and_hms_milli_opt( &self, hour: u32, @@ -130,7 +144,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or microsecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")] #[inline] + #[must_use] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } @@ -141,6 +157,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or microsecond. #[inline] + #[must_use] pub fn and_hms_micro_opt( &self, hour: u32, @@ -156,7 +173,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")] #[inline] + #[must_use] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } @@ -167,6 +186,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. #[inline] + #[must_use] pub fn and_hms_nano_opt( &self, hour: u32, @@ -180,7 +200,9 @@ impl Date { /// Makes a new `Date` for the next date. /// /// Panics when `self` is the last representable date. + #[deprecated(since = "0.4.23", note = "Use succ_opt() instead")] #[inline] + #[must_use] pub fn succ(&self) -> Date { self.succ_opt().expect("out of bound") } @@ -189,6 +211,7 @@ impl Date { /// /// Returns `None` when `self` is the last representable date. #[inline] + #[must_use] pub fn succ_opt(&self) -> Option> { self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone())) } @@ -196,7 +219,9 @@ impl Date { /// Makes a new `Date` for the prior date. /// /// Panics when `self` is the first representable date. + #[deprecated(since = "0.4.23", note = "Use pred_opt() instead")] #[inline] + #[must_use] pub fn pred(&self) -> Date { self.pred_opt().expect("out of bound") } @@ -205,18 +230,21 @@ impl Date { /// /// Returns `None` when `self` is the first representable date. #[inline] + #[must_use] pub fn pred_opt(&self) -> Option> { self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) } /// Retrieves an associated offset from UTC. #[inline] + #[must_use] pub fn offset(&self) -> &Tz::Offset { &self.offset } /// Retrieves an associated time zone. #[inline] + #[must_use] pub fn timezone(&self) -> Tz { TimeZone::from_offset(&self.offset) } @@ -224,40 +252,45 @@ impl Date { /// Changes the associated time zone. /// This does not change the actual `Date` (but will change the string representation). #[inline] + #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> Date { tz.from_utc_date(&self.date) } - /// Adds given `Duration` to the current date. + /// Adds given `TimeDelta` to the current date. /// /// Returns `None` when it will result in overflow. #[inline] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_add_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + #[must_use] + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { + let date = self.date.checked_add_signed(rhs)?; + Some(Date { date, offset: self.offset }) } - /// Subtracts given `Duration` from the current date. + /// Subtracts given `TimeDelta` from the current date. /// /// Returns `None` when it will result in overflow. #[inline] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_sub_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + #[must_use] + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { + let date = self.date.checked_sub_signed(rhs)?; + Some(Date { date, offset: self.offset }) } /// Subtracts another `Date` from the current date. - /// Returns a `Duration` of integral numbers. + /// Returns a `TimeDelta` of integral numbers. /// /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. + /// as all possible output fits in the range of `TimeDelta`. #[inline] - pub fn signed_duration_since(self, rhs: Date) -> OldDuration { + #[must_use] + pub fn signed_duration_since(self, rhs: Date) -> TimeDelta { self.date.signed_duration_since(rhs.date) } /// Returns a view to the naive UTC date. #[inline] + #[must_use] pub fn naive_utc(&self) -> NaiveDate { self.date } @@ -268,9 +301,21 @@ impl Date { /// because the offset is restricted to never exceed one day, /// but provided for the consistency. #[inline] + #[must_use] pub fn naive_local(&self) -> NaiveDate { self.date } + + /// Returns the number of whole years from the given `base` until `self`. + #[must_use] + pub fn years_since(&self, base: Self) -> Option { + self.date.years_since(base.date) + } + + /// The minimum possible `Date`. + pub const MIN_UTC: Date = Date { date: NaiveDate::MIN, offset: Utc }; + /// The maximum possible `Date`. + pub const MAX_UTC: Date = Date { date: NaiveDate::MAX, offset: Utc }; } /// Maps the local date to other date with given conversion function. @@ -286,8 +331,9 @@ where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -297,26 +343,19 @@ where } /// Formats the date with the specified format string. - /// See the [`::format::strftime`] module + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - /// - /// # Example - /// ```rust - /// use chrono::prelude::*; - /// - /// let date_time: Date = Utc.ymd(2017, 04, 02); - /// let formatted = format!("{}", date_time.format("%d/%m/%Y")); - /// assert_eq!(formatted, "02/04/2017"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Formats the date with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] + #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, @@ -336,10 +375,11 @@ where } /// Formats the date with the specified format string and locale. - /// See the [`::format::strftime`] module + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] + #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, @@ -437,7 +477,7 @@ impl Eq for Date {} impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { - self.date.partial_cmp(&other.date) + Some(self.cmp(other)) } } @@ -453,36 +493,55 @@ impl hash::Hash for Date { } } -impl Add for Date { +impl Add for Date { type Output = Date; #[inline] - fn add(self, rhs: OldDuration) -> Date { - self.checked_add_signed(rhs).expect("`Date + Duration` overflowed") + #[track_caller] + fn add(self, rhs: TimeDelta) -> Date { + self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed") + } +} + +impl AddAssign for Date { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed"); } } -impl Sub for Date { +impl Sub for Date { type Output = Date; #[inline] - fn sub(self, rhs: OldDuration) -> Date { - self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed") + #[track_caller] + fn sub(self, rhs: TimeDelta) -> Date { + self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed") + } +} + +impl SubAssign for Date { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed"); } } impl Sub> for Date { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: Date) -> OldDuration { + fn sub(self, rhs: Date) -> TimeDelta { self.signed_duration_since(rhs) } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) } } @@ -491,6 +550,128 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Date +where + Tz::Offset: defmt::Format, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}{}", self.naive_local(), self.offset); + } +} + +// Note that implementation of Arbitrary cannot be automatically derived for Date, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl<'a, Tz> arbitrary::Arbitrary<'a> for Date +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let date = NaiveDate::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(Date::from_utc(date, offset)) + } +} + +#[cfg(test)] +mod tests { + use super::Date; + + use crate::{FixedOffset, NaiveDate, TimeDelta, Utc}; + + #[cfg(feature = "clock")] + use crate::offset::{Local, TimeZone}; + + #[test] + #[cfg(feature = "clock")] + fn test_years_elapsed() { + const WEEKS_PER_YEAR: f32 = 52.1775; + + // This is always at least one year because 1 year = 52.1775 weeks. + let one_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + // A bit more than 2 years. + let two_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + + assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); + + // If the given DateTime is later than now, the function will always return 0. + let future = Utc::today() + TimeDelta::weeks(12); + assert_eq!(Utc::today().years_since(future), None); + } + + #[test] + fn test_date_add_assign() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + let date = Date::::from_utc(naivedate, Utc); + let mut date_add = date; + + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + TimeDelta::days(5)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + TimeDelta::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_add_assign_local() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + + let date = Local.from_utc_date(&naivedate); + let mut date_add = date; + + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); + } + + #[test] + fn test_date_sub_assign() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + let date = Date::::from_utc(naivedate, Utc); + let mut date_sub = date; + + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - TimeDelta::days(5)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - TimeDelta::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_sub_assign_local() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + + let date = Local.from_utc_date(&naivedate); + let mut date_sub = date; + + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); } } diff --git a/src/datetime.rs b/src/datetime.rs deleted file mode 100644 index a5594cadab..0000000000 --- a/src/datetime.rs +++ /dev/null @@ -1,3053 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 date and time with time zone. - -use core::cmp::Ordering; -use core::ops::{Add, Sub}; -use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; -#[cfg(any(feature = "std", test))] -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::string::{String, ToString}; -#[cfg(feature = "std")] -use std::string::ToString; - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -#[cfg(feature = "unstable-locales")] -use format::Locale; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item}; -use naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -use offset::Local; -use offset::{FixedOffset, Offset, TimeZone, Utc}; -use Date; -use {Datelike, Timelike, Weekday}; - -/// Specific formatting options for seconds. This may be extended in the -/// future, so exhaustive matching in external code is not recommended. -/// -/// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SecondsFormat { - /// Format whole seconds only, with no decimal point nor subseconds. - Secs, - - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). - Millis, - - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). - Micros, - - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). - Nanos, - - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). - AutoSi, - - // Do not match against this. - #[doc(hidden)] - __NonExhaustive, -} - -/// ISO 8601 combined date and time with time zone. -/// -/// There are some constructors implemented here (the `from_*` methods), but -/// the general-purpose constructors are all via the methods on the -/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. -#[derive(Clone)] -pub struct DateTime { - datetime: NaiveDateTime, - offset: Tz::Offset, -} - -/// The minimum possible `DateTime`. -pub const MIN_DATETIME: DateTime = DateTime { datetime: naive::MIN_DATETIME, offset: Utc }; -/// The maximum possible `DateTime`. -pub const MAX_DATETIME: DateTime = DateTime { datetime: naive::MAX_DATETIME, offset: Utc }; - -impl DateTime { - /// Makes a new `DateTime` with given *UTC* datetime and offset. - /// The local datetime should be constructed via the `TimeZone` trait. - /// - /// # Example - /// - /// ``` - /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; - /// - /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - /// assert_eq!(Utc.timestamp(61, 0), dt); - /// ``` - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. - #[inline] - pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - DateTime { datetime: datetime, offset: offset } - } - - /// Makes a new `DateTime` with given **local** datetime and offset that - /// presents local timezone. - /// - /// # Example - /// - /// ``` - /// use chrono::DateTime; - /// use chrono::naive::NaiveDate; - /// use chrono::offset::{Utc, FixedOffset}; - /// - /// let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); - /// let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - /// - /// let timezone_east = FixedOffset::east(8 * 60 * 60); - /// let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); - /// let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); - /// - /// let timezone_west = FixedOffset::west(7 * 60 * 60); - /// let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); - /// let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); - - /// assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); - /// assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); - /// ``` - #[inline] - pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - let datetime_utc = datetime - offset.fix(); - - DateTime { datetime: datetime_utc, offset: offset } - } - - /// Retrieves a date component - /// - /// Unless you are immediately planning on turning this into a `DateTime` - /// with the same Timezone you should use the - /// [`date_naive`](DateTime::date_naive) method. - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let date: Date = Utc.ymd(2020, 1, 1); - /// let dt: DateTime = date.and_hms(0, 0, 0); - /// - /// assert_eq!(dt.date(), date); - /// - /// assert_eq!(dt.date().and_hms(1, 1, 1), date.and_hms(1, 1, 1)); - /// ``` - #[inline] - pub fn date(&self) -> Date { - Date::from_utc(self.naive_local().date(), self.offset.clone()) - } - - /// Retrieves the Date without an associated timezone - /// - /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, - /// so should be preferred to [`Date`] any time you truly want to operate on Dates. - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let date: DateTime = Utc.ymd(2020, 1, 1).and_hms(0, 0, 0); - /// let other: DateTime = FixedOffset::east(23).ymd(2020, 1, 1).and_hms(0, 0, 0); - /// assert_eq!(date.date_naive(), other.date_naive()); - /// ``` - #[inline] - pub fn date_naive(&self) -> NaiveDate { - let local = self.naive_local(); - NaiveDate::from_ymd(local.year(), local.month(), local.day()) - } - - /// Retrieves a time component. - /// Unlike `date`, this is not associated to the time zone. - #[inline] - pub fn time(&self) -> NaiveTime { - self.datetime.time() + self.offset.fix() - } - - /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC - /// (aka "UNIX timestamp"). - #[inline] - pub fn timestamp(&self) -> i64 { - self.datetime.timestamp() - } - - /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584 Million. (If this is a problem, please file - /// an issue to let me know what domain needs millisecond precision over - /// billions of years, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_millis(&self) -> i64 { - self.datetime.timestamp_millis() - } - - /// Returns the number of non-leap-microseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584 Thousand. (If this is a problem, please file - /// an issue to let me know what domain needs microsecond precision over - /// millenia, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_micros(), 1_000_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_micros(&self) -> i64 { - self.datetime.timestamp_micros() - } - - /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584. (If this is a problem, please file - /// an issue to let me know what domain needs nanosecond precision over - /// millennia, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() - } - - /// Returns the number of milliseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999 - /// - /// note: this is not the number of milliseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_millis(&self) -> u32 { - self.datetime.timestamp_subsec_millis() - } - - /// Returns the number of microseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999_999 - /// - /// note: this is not the number of microseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_micros(&self) -> u32 { - self.datetime.timestamp_subsec_micros() - } - - /// Returns the number of nanoseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999_999_999 - /// - /// note: this is not the number of nanoseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_nanos(&self) -> u32 { - self.datetime.timestamp_subsec_nanos() - } - - /// Retrieves an associated offset from UTC. - #[inline] - pub fn offset(&self) -> &Tz::Offset { - &self.offset - } - - /// Retrieves an associated time zone. - #[inline] - pub fn timezone(&self) -> Tz { - TimeZone::from_offset(&self.offset) - } - - /// Changes the associated time zone. - /// This does not change the actual `DateTime` (but will change the string representation). - #[inline] - pub fn with_timezone(&self, tz: &Tz2) -> DateTime { - tz.from_utc_datetime(&self.datetime) - } - - /// Adds given `Duration` to the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_add_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts given `Duration` from the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_sub_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts another `DateTime` from the current date and time. - /// This does not overflow or underflow at all. - #[inline] - pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { - self.datetime.signed_duration_since(rhs.datetime) - } - - /// Returns a view to the naive UTC datetime. - #[inline] - pub fn naive_utc(&self) -> NaiveDateTime { - self.datetime - } - - /// Returns a view to the naive local datetime. - #[inline] - pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() - } -} - -impl Default for DateTime { - fn default() -> Self { - Utc.from_utc_datetime(&NaiveDateTime::default()) - } -} - -#[cfg(feature = "clock")] -impl Default for DateTime { - fn default() -> Self { - Local.from_utc_datetime(&NaiveDateTime::default()) - } -} - -impl Default for DateTime { - fn default() -> Self { - FixedOffset::west(0).from_utc_datetime(&NaiveDateTime::default()) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by - /// this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone - /// difference. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local - /// time. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in - /// timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned - /// by this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Maps the local datetime to other datetime with given conversion function. -fn map_local(dt: &DateTime, mut f: F) -> Option> -where - F: FnMut(NaiveDateTime) -> Option, -{ - f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) -} - -impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// RFC 2822 is the internet message standard that specifices the - /// representation of times in HTTP and email headers. - /// - /// ``` - /// # use chrono::{DateTime, FixedOffset, TimeZone}; - /// assert_eq!( - /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), - /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) - /// ); - /// ``` - pub fn parse_from_rfc2822(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. - pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses a string with the specified format string and returns a new - /// [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// See the [`::format::strftime`] module on the supported escape - /// sequences. - /// - /// See also [`TimeZone::datetime_from_str`] which gives a local - /// [`DateTime`] on specific time zone. - /// - /// Note that this method *requires a timezone* in the string. See - /// [`NaiveDateTime::parse_from_str`] - /// for a version that does not require a timezone in the to-be-parsed str. - /// - /// # Example - /// - /// ```rust - /// use chrono::{DateTime, FixedOffset, TimeZone}; - /// - /// let dt = DateTime::parse_from_str( - /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_datetime() - } -} - -impl DateTime -where - Tz::Offset: fmt::Display, -{ - /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc2822(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per a `SecondsFormat`. - /// - /// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as - /// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use - /// [`Fixed::TimezoneOffsetColon`] - /// - /// # Examples - /// - /// ```rust - /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); - /// - /// let pst = FixedOffset::east(8 * 60 * 60); - /// let dt = pst.ymd(2018, 1, 26).and_hms_micro(10, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use format::Numeric::*; - use format::Pad::Zero; - use SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &'static [Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - match ssitem { - None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } - } - - /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) - } - - /// Formats the combined date and time with the specified format string. - /// See the [`::format::strftime`] module - /// on the supported escape sequences. - /// - /// # Example - /// ```rust - /// use chrono::prelude::*; - /// - /// let date_time: DateTime = Utc.ymd(2017, 04, 02).and_hms(12, 50, 32); - /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); - /// assert_eq!(formatted, "02/04/2017 12:50"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Formats the combined date and time with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized_with_items<'a, I, B>( - &self, - items: I, - locale: Locale, - ) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset_and_locale( - Some(local.date()), - Some(local.time()), - &self.offset, - items, - locale, - ) - } - - /// Formats the combined date and time with the specified format string and - /// locale. - /// - /// See the [`::format::strftime`] module on the supported escape - /// sequences. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized<'a>( - &self, - fmt: &'a str, - locale: Locale, - ) -> DelayedFormat> { - self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) - } -} - -impl Datelike for DateTime { - #[inline] - fn year(&self) -> i32 { - self.naive_local().year() - } - #[inline] - fn month(&self) -> u32 { - self.naive_local().month() - } - #[inline] - fn month0(&self) -> u32 { - self.naive_local().month0() - } - #[inline] - fn day(&self) -> u32 { - self.naive_local().day() - } - #[inline] - fn day0(&self) -> u32 { - self.naive_local().day0() - } - #[inline] - fn ordinal(&self) -> u32 { - self.naive_local().ordinal() - } - #[inline] - fn ordinal0(&self) -> u32 { - self.naive_local().ordinal0() - } - #[inline] - fn weekday(&self) -> Weekday { - self.naive_local().weekday() - } - #[inline] - fn iso_week(&self) -> IsoWeek { - self.naive_local().iso_week() - } - - #[inline] - fn with_year(&self, year: i32) -> Option> { - map_local(self, |datetime| datetime.with_year(year)) - } - - #[inline] - fn with_month(&self, month: u32) -> Option> { - map_local(self, |datetime| datetime.with_month(month)) - } - - #[inline] - fn with_month0(&self, month0: u32) -> Option> { - map_local(self, |datetime| datetime.with_month0(month0)) - } - - #[inline] - fn with_day(&self, day: u32) -> Option> { - map_local(self, |datetime| datetime.with_day(day)) - } - - #[inline] - fn with_day0(&self, day0: u32) -> Option> { - map_local(self, |datetime| datetime.with_day0(day0)) - } - - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal(ordinal)) - } - - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) - } -} - -impl Timelike for DateTime { - #[inline] - fn hour(&self) -> u32 { - self.naive_local().hour() - } - #[inline] - fn minute(&self) -> u32 { - self.naive_local().minute() - } - #[inline] - fn second(&self) -> u32 { - self.naive_local().second() - } - #[inline] - fn nanosecond(&self) -> u32 { - self.naive_local().nanosecond() - } - - #[inline] - fn with_hour(&self, hour: u32) -> Option> { - map_local(self, |datetime| datetime.with_hour(hour)) - } - - #[inline] - fn with_minute(&self, min: u32) -> Option> { - map_local(self, |datetime| datetime.with_minute(min)) - } - - #[inline] - fn with_second(&self, sec: u32) -> Option> { - map_local(self, |datetime| datetime.with_second(sec)) - } - - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option> { - map_local(self, |datetime| datetime.with_nanosecond(nano)) - } -} - -// we need them as automatic impls cannot handle associated types -impl Copy for DateTime where ::Offset: Copy {} -unsafe impl Send for DateTime where ::Offset: Send {} - -impl PartialEq> for DateTime { - fn eq(&self, other: &DateTime) -> bool { - self.datetime == other.datetime - } -} - -impl Eq for DateTime {} - -impl PartialOrd> for DateTime { - /// Compare two DateTimes based on their true time, ignoring time zones - /// - /// # Example - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let earlier = Utc.ymd(2015, 5, 15).and_hms(2, 0, 0).with_timezone(&FixedOffset::west(1 * 3600)); - /// let later = Utc.ymd(2015, 5, 15).and_hms(3, 0, 0).with_timezone(&FixedOffset::west(5 * 3600)); - /// - /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); - /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); - /// - /// assert!(later > earlier); - /// ``` - fn partial_cmp(&self, other: &DateTime) -> Option { - self.datetime.partial_cmp(&other.datetime) - } -} - -impl Ord for DateTime { - fn cmp(&self, other: &DateTime) -> Ordering { - self.datetime.cmp(&other.datetime) - } -} - -impl hash::Hash for DateTime { - fn hash(&self, state: &mut H) { - self.datetime.hash(state) - } -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: OldDuration) -> DateTime { - self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") - } -} - -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> DateTime { - self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") - } -} - -impl Sub> for DateTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: DateTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -impl fmt::Debug for DateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) - } -} - -impl fmt::Display for DateTime -where - Tz::Offset: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.naive_local(), self.offset) - } -} - -/// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. -/// -/// All of these examples are equivalent: -/// ``` -/// # use chrono::{DateTime, Utc}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); -/// ``` -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Utc)) - } -} - -/// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. -/// -/// All of these examples are equivalent: -/// ``` -/// # use chrono::{DateTime, Local}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); -/// ``` -#[cfg(feature = "clock")] -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Local)) - } -} - -#[cfg(any(feature = "std", test))] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { - Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), - Err(e) => { - // unlikely but should be handled - let dur = e.duration(); - let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); - if nsec == 0 { - (-sec, 0) - } else { - (-sec - 1, 1_000_000_000 - nsec) - } - } - }; - Utc.timestamp(sec, nsec) - } -} - -#[cfg(feature = "clock")] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - DateTime::::from(t).with_timezone(&Local) - } -} - -#[cfg(any(feature = "std", test))] -impl From> for SystemTime { - fn from(dt: DateTime) -> SystemTime { - use std::time::Duration; - - let sec = dt.timestamp(); - let nsec = dt.timestamp_subsec_nanos(); - if sec < 0 { - // unlikely but should be handled - UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) - } else { - UNIX_EPOCH + Duration::new(sec as u64, nsec) - } - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From for DateTime { - fn from(date: js_sys::Date) -> DateTime { - DateTime::::from(&date) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From<&js_sys::Date> for DateTime { - fn from(date: &js_sys::Date) -> DateTime { - Utc.timestamp_millis(date.get_time() as i64) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From> for js_sys::Date { - /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, - /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 - /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. - fn from(date: DateTime) -> js_sys::Date { - let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); - js_sys::Date::new(&js_millis) - } -} - -#[test] -fn test_auto_conversion() { - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0); - let utc_dt2: DateTime = cdt_dt.into(); - assert_eq!(utc_dt, utc_dt2); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) -where - FUtc: Fn(&DateTime) -> Result, - FFixed: Fn(&DateTime) -> Result, - E: ::core::fmt::Debug, -{ - assert_eq!( - to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06Z""#.into()) - ); - - assert_eq!( - to_string_fixed(&FixedOffset::east(3660).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:01""#.into()) - ); - assert_eq!( - to_string_fixed(&FixedOffset::east(3650).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) - ); -} - -#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - // should check against the offset as well (the normal DateTime comparison will ignore them) - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(FixedOffset::east(60 * 60 + 23 * 60).ymd(2014, 7, 24).and_hms(13, 57, 6))) - ); - - // we don't know the exact local offset but we can check that - // the conversion didn't change the instant itself - assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - assert_eq!( - local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - - assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); -} - -#[cfg(all(test, feature = "clock", feature = "rustc-serialize"))] -fn test_decodable_json_timestamps( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - norm(&fixed_from_str("0").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&fixed_from_str("-1").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - ); - assert_eq!( - *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.ymd(1969, 12, 31).and_hms(23, 59, 59) - ); -} - -#[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { - use super::DateTime; - use core::fmt; - use core::ops::Deref; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for DateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - // lik? function to convert a LocalResult into a serde-ish Result - fn from(me: LocalResult, d: &mut D) -> Result - where - D: Decoder, - T: fmt::Display, - { - match me { - LocalResult::None => Err(d.error("value is not a legal timestamp")), - LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - LocalResult::Single(val) => Ok(val), - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map_err(|_| d.error("invalid date and time")) - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map(|dt| dt.with_timezone(&Utc)) - .map_err(|_| d.error("invalid date and time")) - } - } - - /// A [`DateTime`] that can be deserialized from a timestamp - /// - /// A timestamp here is seconds since the epoch - #[derive(Debug)] - pub struct TsSeconds(DateTime); - - #[allow(deprecated)] - impl From> for DateTime { - /// Pull the inner DateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> DateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - #[cfg(feature = "clock")] - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - match d.read_str()?.parse::>() { - Ok(dt) => Ok(dt.with_timezone(&Local)), - Err(_) => Err(d.error("invalid date and time")), - } - } - } - - #[cfg(feature = "clock")] - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d) - .map(|dt| TsSeconds(dt.with_timezone(&Local))) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode, json::encode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode, json::decode, json::decode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamps(json::decode, json::decode, json::decode); - } -} - -/// documented at re-export site -#[cfg(feature = "serde")] -pub mod serde { - use super::DateTime; - use core::fmt; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use serdelib::{de, ser}; - use {ne_timestamp, SerdeError}; - - #[doc(hidden)] - #[derive(Debug)] - pub struct SecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct NanoSecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct MicroSecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct MilliSecondsTimestampVisitor; - - // lik? function to convert a LocalResult into a serde-ish Result - fn serde_from(me: LocalResult, ts: &V) -> Result - where - E: de::Error, - V: fmt::Display, - T: fmt::Display, - { - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min: min, max: max })) - } - LocalResult::Single(val) => Ok(val), - } - } - - /// Serialize into a rfc3339 time string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serializations. - impl ser::Serialize for DateTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - // Debug formatting is correct RFC3339, and it allows Zulu. - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct DateTimeVisitor; - - impl<'de> de::Visitor<'de> for DateTimeVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date and time string or a unix timestamp") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - /// Deserialize a value that optionally includes a timezone offset in its - /// string representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor) - } - } - - /// Deserialize into a UTC value - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) - } - } - - /// Deserialize a value that includes no timezone in its string - /// representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - #[cfg(feature = "clock")] - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) - } - } - - /// Ser/de to/from timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, NanoSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a [`DateTime`] from a nanosecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NanoSecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in nanoseconds") - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt( - (value / 1_000_000_000) as i64, - (value % 1_000_000_000) as u32, - ), - &value, - ) - } - } - } - - /// Ser/de to/from timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::NanoSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a nanosecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionNanoSecondsTimestampVisitor) - } - - struct OptionNanoSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in nanoseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in microseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_microseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_microseconds { - use core::fmt; - - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, MicroSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of microseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_micro_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_micros()) - } - - /// Deserialize a `DateTime` from a microsecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_micro_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MicroSecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in microseconds") - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1_000_000, ((value % 1_000_000) * 1_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt( - (value / 1_000_000) as i64, - ((value % 1_000_000) * 1_000) as u32, - ), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in microseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_microseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_microseconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::MicroSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_micro_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a microsecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_micro_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionMicroSecondsTimestampVisitor) - } - - struct OptionMicroSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in microseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, MilliSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a millisecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) - } - - impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds") - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::MilliSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a millisecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::prelude::*; - /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// #[serde(untagged)] - /// enum E { - /// V(T), - /// } - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// struct S { - /// #[serde(default, deserialize_with = "from_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result<(), serde_json::Error> { - /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); - /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; - /// assert_eq!(s, E::V(S { time: None })); - /// let t: E = serde_json::from_str(r#"{}"#)?; - /// assert_eq!(t, E::V(S { time: None })); - /// # Ok(()) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionMilliSecondsTimestampVisitor) - .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) - } - - struct OptionMilliSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, SecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(SecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value, 0), &value) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value as i64, 0), &value) - } - } - } - - /// Ser/de to/from optional timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::SecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a seconds timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionSecondsTimestampVisitor) - } - - struct OptionSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(SecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string, self::serde_json::to_string); - } - - #[cfg(feature = "clock")] - #[test] - fn test_serde_deserialize() { - super::test_decodable_json( - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), - ); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); - let encoded = serialize(&dt, Infinite).unwrap(); - let decoded: DateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - assert_eq!(dt.offset(), decoded.offset()); - } -} - -#[cfg(test)] -mod tests { - use super::DateTime; - use naive::{NaiveDate, NaiveTime}; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, TimeZone, Utc}; - use oldtime::Duration; - use std::time::{SystemTime, UNIX_EPOCH}; - #[cfg(feature = "clock")] - use Datelike; - - #[test] - #[allow(non_snake_case)] - fn test_datetime_offset() { - let Est = FixedOffset::west(5 * 60 * 60); - let Edt = FixedOffset::west(4 * 60 * 60); - let Kst = FixedOffset::east(9 * 60 * 60); - - assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); - assert_eq!( - format!("{}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 -04:00" - ); - assert_eq!( - format!("{}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 +09:00" - ); - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09+09:00" - ); - - // edge cases - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00+09:00" - ); - assert_eq!( - format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59Z" - ); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59+09:00" - ); - - let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(dt, Edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); - assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); - assert_eq!( - dt.signed_duration_since(Edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), - Duration::seconds(-7 * 3600 - 3 * 60 - 3) - ); - - assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); - assert_eq!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Edt); - assert!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != Est); - } - - #[test] - fn test_datetime_date_and_time() { - let tz = FixedOffset::east(5 * 60 * 60); - let d = tz.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(d.time(), NaiveTime::from_hms(7, 8, 9)); - assert_eq!(d.date(), tz.ymd(2014, 5, 6)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2014, 5, 6)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::east(4 * 60 * 60); - let d = tz.ymd(2016, 5, 4).and_hms(3, 2, 1); - assert_eq!(d.time(), NaiveTime::from_hms(3, 2, 1)); - assert_eq!(d.date(), tz.ymd(2016, 5, 4)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2016, 5, 4)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::west(13 * 60 * 60); - let d = tz.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert_eq!(d.time(), NaiveTime::from_hms(12, 34, 56)); - assert_eq!(d.date(), tz.ymd(2017, 8, 9)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2017, 8, 9)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let utc_d = Utc.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert!(utc_d < d); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_with_timezone() { - let local_now = Local::now(); - let utc_now = local_now.with_timezone(&Utc); - let local_now2 = utc_now.with_timezone(&Local); - assert_eq!(local_now, local_now2); - } - - #[test] - #[allow(non_snake_case)] - fn test_datetime_rfc2822_and_rfc3339() { - let EDT = FixedOffset::east(5 * 60 * 60); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0000" - ); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); - - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) - ); - } - - #[test] - fn test_rfc3339_opts() { - use SecondsFormat::*; - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); - assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); - assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); - assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); - assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); - - let ut = DateTime::::from_utc(dt.naive_utc(), Utc); - assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); - assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); - assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); - assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); - assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); - assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); - assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); - } - - #[test] - #[should_panic] - fn test_rfc3339_opts_nonexhaustive() { - use SecondsFormat; - let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); - dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); - } - - #[test] - fn test_datetime_from_str() { - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - // no test for `DateTime`, we cannot verify that much. - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); - assert_eq!( - DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) - ); // ignore offset - assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert_eq!( - Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), - Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) - ); - } - - #[test] - fn test_to_string_round_trip() { - let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0); - let _dt: DateTime = dt.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(0)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_to_string_round_trip_with_local() { - let ndt = Local::now(); - let _dt: DateTime = ndt.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_format_with_local() { - // if we are not around the year boundary, local and UTC date should have the same year - let dt = Local::now().with_month(5).unwrap(); - assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_copy() { - // UTC is known to be `Copy`. - let a = Utc::now(); - let b = a; - assert_eq!(a, b); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_send() { - use std::thread; - - // UTC is known to be `Send`. - let a = Utc::now(); - thread::spawn(move || { - let _ = a; - }) - .join() - .unwrap(); - } - - #[test] - fn test_subsecond_part() { - let datetime = Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); - - assert_eq!(1, datetime.timestamp_subsec_millis()); - assert_eq!(1234, datetime.timestamp_subsec_micros()); - assert_eq!(1234567, datetime.timestamp_subsec_nanos()); - } - - #[test] - #[cfg(not(target_os = "windows"))] - fn test_from_system_time() { - use std::time::Duration; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - let nanos = 999_999_999; - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)), - UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - #[cfg(target_os = "windows")] - fn test_from_system_time() { - use std::time::Duration; - - let nanos = 999_999_000; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)), - UNIX_EPOCH - Duration::new(999_999_999, nanos) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - fn test_datetime_format_alignment() { - let datetime = Utc.ymd(2007, 1, 2); - - // Item::Literal - let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>3}", percent)); - assert_eq!("% ", format!("{:<3}", percent)); - assert_eq!(" % ", format!("{:^3}", percent)); - - // Item::Numeric - let year = datetime.format("%Y"); - assert_eq!(" 2007", format!("{:>6}", year)); - assert_eq!("2007 ", format!("{:<6}", year)); - assert_eq!(" 2007 ", format!("{:^6}", year)); - - // Item::Fixed - let tz = datetime.format("%Z"); - assert_eq!(" UTC", format!("{:>5}", tz)); - assert_eq!("UTC ", format!("{:<5}", tz)); - assert_eq!(" UTC ", format!("{:^5}", tz)); - - // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] - let ymd = datetime.format("%Y %B %d"); - let ymd_formatted = "2007 January 02"; - assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); - assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); - assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); - } - - #[test] - fn test_datetime_from_local() { - // 2000-01-12T02:00:00Z - let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - - // 2000-01-12T10:00:00+8:00:00 - let timezone_east = FixedOffset::east(8 * 60 * 60); - let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); - let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); - - // 2000-01-11T19:00:00-7:00:00 - let timezone_west = FixedOffset::west(7 * 60 * 60); - let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); - let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); - - assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); - assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); - } -} diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs new file mode 100644 index 0000000000..2e76c1f92b --- /dev/null +++ b/src/datetime/mod.rs @@ -0,0 +1,2016 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 date and time with time zone. + +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::string::String; +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::fmt::Write; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, hash, str}; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + +#[allow(deprecated)] +use crate::Date; +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use crate::format::Locale; +#[cfg(feature = "alloc")] +use crate::format::{DelayedFormat, SecondsFormat, write_rfc2822, write_rfc3339}; +use crate::format::{ + Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder, + parse_rfc3339, +}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; +use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; +use crate::{expect, try_opt}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// documented at re-export site +#[cfg(feature = "serde")] +pub(super) mod serde; + +#[cfg(test)] +mod tests; + +/// ISO 8601 combined date and time with time zone. +/// +/// There are some constructors implemented here (the `from_*` methods), but +/// the general-purpose constructors are all via the methods on the +/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. +#[derive(Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct DateTime { + datetime: NaiveDateTime, + offset: Tz::Offset, +} + +/// The minimum possible `DateTime`. +#[deprecated(since = "0.4.20", note = "Use DateTime::MIN_UTC instead")] +pub const MIN_DATETIME: DateTime = DateTime::::MIN_UTC; +/// The maximum possible `DateTime`. +#[deprecated(since = "0.4.20", note = "Use DateTime::MAX_UTC instead")] +pub const MAX_DATETIME: DateTime = DateTime::::MAX_UTC; + +impl DateTime { + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. + /// + /// This is a low-level method, intended for use cases such as deserializing a `DateTime` or + /// passing it through FFI. + /// + /// For regular use you will probably want to use a method such as + /// [`TimeZone::from_local_datetime`] or [`NaiveDateTime::and_local_timezone`] instead. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// use chrono::{DateTime, Local}; + /// + /// let dt = Local::now(); + /// // Get components + /// let naive_utc = dt.naive_utc(); + /// let offset = dt.offset().clone(); + /// // Serialize, pass through FFI... and recreate the `DateTime`: + /// let dt_new = DateTime::::from_naive_utc_and_offset(naive_utc, offset); + /// assert_eq!(dt, dt_new); + /// # } + /// ``` + #[inline] + #[must_use] + pub const fn from_naive_utc_and_offset( + datetime: NaiveDateTime, + offset: Tz::Offset, + ) -> DateTime { + DateTime { datetime, offset } + } + + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. + #[inline] + #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_utc_datetime() or DateTime::from_naive_utc_and_offset instead" + )] + pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + DateTime { datetime, offset } + } + + /// Makes a new `DateTime` from a `NaiveDateTime` in *local* time and an `Offset`. + /// + /// # Panics + /// + /// Panics if the local datetime can't be converted to UTC because it would be out of range. + /// + /// This can happen if `datetime` is near the end of the representable range of `NaiveDateTime`, + /// and the offset from UTC pushes it beyond that. + #[inline] + #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_local_datetime() or NaiveDateTime::and_local_timezone instead" + )] + pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + let datetime_utc = datetime - offset.fix(); + + DateTime { datetime: datetime_utc, offset } + } + + /// Retrieves the date component with an associated timezone. + /// + /// Unless you are immediately planning on turning this into a `DateTime` + /// with the same timezone you should use the [`date_naive`](DateTime::date_naive) method. + /// + /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, + /// so should be preferred to [`Date`] any time you truly want to operate on dates. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`Date`]. + #[inline] + #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] + #[allow(deprecated)] + #[must_use] + pub fn date(&self) -> Date { + Date::from_utc(self.naive_local().date(), self.offset.clone()) + } + + /// Retrieves the date component. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`NaiveDate`]. + /// + /// # Example + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// let other: DateTime = + /// FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// assert_eq!(date.date_naive(), other.date_naive()); + /// ``` + #[inline] + #[must_use] + pub fn date_naive(&self) -> NaiveDate { + self.naive_local().date() + } + + /// Retrieves the time component. + #[inline] + #[must_use] + pub fn time(&self) -> NaiveTime { + self.datetime.time() + self.offset.fix() + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC + /// (aka "UNIX timestamp"). + /// + /// The reverse operation of creating a [`DateTime`] from a timestamp can be performed + /// using [`from_timestamp`](DateTime::from_timestamp) or [`TimeZone::timestamp_opt`]. + /// + /// ``` + /// use chrono::{DateTime, TimeZone, Utc}; + /// + /// let dt: DateTime = Utc.with_ymd_and_hms(2015, 5, 15, 0, 0, 0).unwrap(); + /// assert_eq!(dt.timestamp(), 1431648000); + /// + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp(&self) -> i64 { + let gregorian_day = self.datetime.date().num_days_from_ce() as i64; + let seconds_from_midnight = self.datetime.time().num_seconds_from_midnight() as i64; + (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight + } + + /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_milli_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_millis(), 1_444); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_milli_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_millis(&self) -> i64 { + let as_ms = self.timestamp() * 1000; + as_ms + self.timestamp_subsec_millis() as i64 + } + + /// Returns the number of non-leap-microseconds since January 1, 1970 UTC. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_micro_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_micros(), 1_000_444); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_micro_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_micros(&self) -> i64 { + let as_us = self.timestamp() * 1_000_000; + as_us + self.timestamp_subsec_micros() as i64 + } + + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_nanos(&self) -> i64 { + expect( + self.timestamp_nanos_opt(), + "value can not be represented in a timestamp with nanosecond precision.", + ) + } + + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_nano_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_nano_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_192) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(-9_223_372_036_854_775_808)); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_807) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(9_223_372_036_854_775_807)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_191) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_808) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_nanos_opt(&self) -> Option { + let mut timestamp = self.timestamp(); + let mut subsec_nanos = self.timestamp_subsec_nanos() as i64; + // `(timestamp * 1_000_000_000) + subsec_nanos` may create a temporary that underflows while + // the final value can be represented as an `i64`. + // As workaround we converting the negative case to: + // `((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000)`` + // + // Also see . + if timestamp < 0 { + subsec_nanos -= 1_000_000_000; + timestamp += 1; + } + try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(subsec_nanos) + } + + /// Returns the number of milliseconds since the last second boundary. + /// + /// In event of a leap second this may exceed 999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_millis(&self) -> u32 { + self.timestamp_subsec_nanos() / 1_000_000 + } + + /// Returns the number of microseconds since the last second boundary. + /// + /// In event of a leap second this may exceed 999,999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_micros(&self) -> u32 { + self.timestamp_subsec_nanos() / 1_000 + } + + /// Returns the number of nanoseconds since the last second boundary + /// + /// In event of a leap second this may exceed 999,999,999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_nanos(&self) -> u32 { + self.datetime.time().nanosecond() + } + + /// Retrieves an associated offset from UTC. + #[inline] + #[must_use] + pub const fn offset(&self) -> &Tz::Offset { + &self.offset + } + + /// Retrieves an associated time zone. + #[inline] + #[must_use] + pub fn timezone(&self) -> Tz { + TimeZone::from_offset(&self.offset) + } + + /// Changes the associated time zone. + /// The returned `DateTime` references the same instant of time from the perspective of the + /// provided time zone. + #[inline] + #[must_use] + pub fn with_timezone(&self, tz: &Tz2) -> DateTime { + tz.from_utc_datetime(&self.datetime) + } + + /// Fix the offset from UTC to its current value, dropping the associated timezone information. + /// This is useful for converting a generic `DateTime` to `DateTime`. + #[inline] + #[must_use] + pub fn fixed_offset(&self) -> DateTime { + self.with_timezone(&self.offset().fix()) + } + + /// Turn this `DateTime` into a `DateTime`, dropping the offset and associated timezone + /// information. + #[inline] + #[must_use] + pub const fn to_utc(&self) -> DateTime { + DateTime { datetime: self.datetime, offset: Utc } + } + + /// Adds given `TimeDelta` to the current date and time. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + #[inline] + #[must_use] + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { + let datetime = self.datetime.checked_add_signed(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Adds given `Months` to the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// See [`NaiveDate::checked_add_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). + #[must_use] + pub fn checked_add_months(self, months: Months) -> Option> { + // `NaiveDate::checked_add_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_add_months(months)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + + /// Subtracts given `TimeDelta` from the current date and time. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + #[inline] + #[must_use] + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { + let datetime = self.datetime.checked_sub_signed(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Subtracts given `Months` from the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// See [`NaiveDate::checked_sub_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). + #[must_use] + pub fn checked_sub_months(self, months: Months) -> Option> { + // `NaiveDate::checked_sub_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_sub_months(months)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + + /// Add a duration in [`Days`] to the date part of the `DateTime`. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). + #[must_use] + pub fn checked_add_days(self, days: Days) -> Option { + if days == Days::new(0) { + return Some(self); + } + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_add_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt <= &DateTime::::MAX_UTC) + } + + /// Subtract a duration in [`Days`] from the date part of the `DateTime`. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). + #[must_use] + pub fn checked_sub_days(self, days: Days) -> Option { + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_sub_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC) + } + + /// Subtracts another `DateTime` from the current date and time. + /// This does not overflow or underflow at all. + #[inline] + #[must_use] + pub fn signed_duration_since( + self, + rhs: impl Borrow>, + ) -> TimeDelta { + self.datetime.signed_duration_since(rhs.borrow().datetime) + } + + /// Returns a view to the naive UTC datetime. + #[inline] + #[must_use] + pub const fn naive_utc(&self) -> NaiveDateTime { + self.datetime + } + + /// Returns a view to the naive local datetime. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local datetime outside of the + /// representable range of a [`NaiveDateTime`]. + #[inline] + #[must_use] + pub fn naive_local(&self) -> NaiveDateTime { + self.datetime + .checked_add_offset(self.offset.fix()) + .expect("Local time out of range for `NaiveDateTime`") + } + + /// Returns the naive local datetime. + /// + /// This makes use of the buffer space outside of the representable range of values of + /// `NaiveDateTime`. The result can be used as intermediate value, but should never be exposed + /// outside chrono. + #[inline] + #[must_use] + pub(crate) fn overflowing_naive_local(&self) -> NaiveDateTime { + self.datetime.overflowing_add_offset(self.offset.fix()) + } + + /// Retrieve the elapsed years from now to the given [`DateTime`]. + /// + /// # Errors + /// + /// Returns `None` if `base > self`. + #[must_use] + pub fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + let earlier_time = + (self.month(), self.day(), self.time()) < (base.month(), base.day(), base.time()); + + years -= match earlier_time { + true => 1, + false => 0, + }; + + match years >= 0 { + true => Some(years as u32), + false => None, + } + } + + /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + /// + /// # Panics + /// + /// Panics if the date can not be represented in this format: the year may not be negative and + /// can not have more than 4 digits. + #[cfg(feature = "alloc")] + #[must_use] + #[track_caller] + pub fn to_rfc2822(&self) -> String { + let mut result = String::with_capacity(32); + write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) + .expect("date cannot be represented by RFC 2822"); + result + } + + /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_rfc3339(&self) -> String { + // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. + let mut result = String::with_capacity(32); + let naive = self.overflowing_naive_local(); + let offset = self.offset.fix(); + write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Return an RFC 3339 and ISO 8601 date and time string with subseconds + /// formatted as per `SecondsFormat`. + /// + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses + /// [`Fixed::TimezoneOffsetColon`] + /// + /// # Examples + /// + /// ```rust + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(18, 30, 9, 453_829) + /// .unwrap() + /// .and_utc(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T18:30:09Z"); + /// + /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let dt = pst + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(10, 30, 9, 453_829) + /// .unwrap(), + /// ) + /// .unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T10:30:09+08:00"); + /// ``` + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { + let mut result = String::with_capacity(38); + write_rfc3339(&mut result, self.naive_local(), self.offset.fix(), secform, use_z) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Set the time to a new fixed time on the existing date. + /// + /// # Errors + /// + /// Returns `LocalResult::None` if the datetime is at the edge of the representable range for a + /// `DateTime`, and `with_time` would push the value in UTC out of range. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// use chrono::{Local, NaiveTime}; + /// + /// let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap(); + /// let today_noon = Local::now().with_time(noon); + /// let today_midnight = Local::now().with_time(NaiveTime::MIN); + /// + /// assert_eq!(today_noon.single().unwrap().time(), noon); + /// assert_eq!(today_midnight.single().unwrap().time(), NaiveTime::MIN); + /// # } + /// ``` + #[must_use] + pub fn with_time(&self, time: NaiveTime) -> LocalResult { + self.timezone().from_local_datetime(&self.overflowing_naive_local().date().and_time(time)) + } + + /// The minimum possible `DateTime`. + pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; + /// The maximum possible `DateTime`. + pub const MAX_UTC: DateTime = DateTime { datetime: NaiveDateTime::MAX, offset: Utc }; +} + +impl DateTime { + /// Makes a new `DateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). + /// + /// This is a convenience wrapper around [`DateTime::from_timestamp`], + /// which is useful in functions like [`Iterator::map`] to avoid a closure. + /// + /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]; if you need to create a + /// `DateTime` with more precision, use [`DateTime::from_timestamp_micros`], + /// [`DateTime::from_timestamp_millis`], or [`DateTime::from_timestamp_nanos`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds, + /// otherwise returns `Some(DateTime {...})`. + /// + /// # Examples + /// + /// Using [`Option::and_then`]: + /// + /// ``` + /// # use chrono::DateTime; + /// let maybe_timestamp: Option = Some(1431648000); + /// let maybe_dt = maybe_timestamp.and_then(DateTime::from_timestamp_secs); + /// + /// assert!(maybe_dt.is_some()); + /// assert_eq!(maybe_dt.unwrap().to_string(), "2015-05-15 00:00:00 UTC"); + /// ``` + /// + /// Using [`Iterator::map`]: + /// + /// ``` + /// # use chrono::{DateTime, Utc}; + /// let v = vec![i64::MIN, 1_000_000_000, 1_234_567_890, i64::MAX]; + /// let timestamps: Vec>> = v + /// .into_iter() + /// .map(DateTime::from_timestamp_secs) + /// .collect(); + /// + /// assert_eq!(vec![ + /// None, + /// Some(DateTime::parse_from_rfc3339("2001-09-09 01:46:40Z").unwrap().to_utc()), + /// Some(DateTime::parse_from_rfc3339("2009-02-13 23:31:30Z").unwrap().to_utc()), + /// None, + /// ], timestamps); + /// ``` + /// + #[inline] + #[must_use] + pub const fn from_timestamp_secs(secs: i64) -> Option { + Self::from_timestamp(secs, 0) + } + + /// Makes a new `DateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp) and + /// [`timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let dt = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option { + let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY; + let secs = secs.rem_euclid(86_400); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + let date = try_opt!(NaiveDate::from_num_days_from_ce_opt(days as i32)); + let time = try_opt!(NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs)); + Some(date.and_time(time).and_utc()) + } + + /// Makes a new `DateTime` from the number of non-leap milliseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_millis`](DateTime::timestamp_millis). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of milliseconds, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let dt = DateTime::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); + /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_millis(millis: i64) -> Option { + let secs = millis.div_euclid(1000); + let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; + Self::from_timestamp(secs, nsecs) + } + + /// Creates a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_micros`](DateTime::timestamp_micros). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_micros`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_micros: i64 = 1662921288000000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_micros: i64 = -2208936075000000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_micros(micros: i64) -> Option { + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + Self::from_timestamp(secs, nsecs) + } + + /// Creates a new [`DateTime`] from the number of non-leap nanoseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_nanos`](DateTime::timestamp_nanos). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_nanos`] or [`DateTime::with_timezone`]. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. Because all values can + /// be represented as a `DateTime` this method never fails. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_nanos: i64 = 1662921288_000_000_000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Self { + let secs = nanos.div_euclid(1_000_000_000); + let nsecs = nanos.rem_euclid(1_000_000_000) as u32; + expect(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range") + } + + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. + pub const UNIX_EPOCH: Self = + expect(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN).and_utc(); +} + +impl Default for DateTime { + fn default() -> Self { + Utc.from_utc_datetime(&NaiveDateTime::default()) + } +} + +#[cfg(feature = "clock")] +impl Default for DateTime { + fn default() -> Self { + Local.from_utc_datetime(&NaiveDateTime::default()) + } +} + +impl Default for DateTime { + fn default() -> Self { + FixedOffset::west_opt(0).unwrap().from_utc_datetime(&NaiveDateTime::default()) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by + /// this will be created with a fixed timezone offset of 0. + fn from(src: DateTime) -> Self { + src.with_timezone(&FixedOffset::east_opt(0).unwrap()) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone + /// difference. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local + /// time. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in + /// timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. + fn from(src: DateTime) -> Self { + src.with_timezone(&src.offset().fix()) + } +} + +/// Maps the local datetime to other datetime with given conversion function. +fn map_local(dt: &DateTime, mut f: F) -> Option> +where + F: FnMut(NaiveDateTime) -> Option, +{ + f(dt.overflowing_naive_local()) + .and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC && dt <= &DateTime::::MAX_UTC) +} + +impl DateTime { + /// Parses an RFC 2822 date-and-time string into a `DateTime` value. + /// + /// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`) + /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. + /// + /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP + /// and email headers. It is the 2001 revision of RFC 822, and is itself revised as RFC 5322 in + /// 2008. + /// + /// # Support for the obsolete date format + /// + /// - A 2-digit year is interpreted to be a year in 1950-2049. + /// - The standard allows comments and whitespace between many of the tokens. See [4.3] and + /// [Appendix A.5] + /// - Single letter 'military' time zone names are parsed as a `-0000` offset. + /// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because + /// the meaning is now ambiguous, the standard says they should be considered as `-0000` + /// unless there is out-of-band information confirming their meaning. + /// The exception is `Z`, which remains identical to `+0000`. + /// + /// [4.3]: https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + /// [Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 + /// + /// # Example + /// + /// ``` + /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// assert_eq!( + /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), + /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// ``` + pub fn parse_from_rfc2822(s: &str) -> ParseResult> { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_datetime() + } + + /// Parses an RFC 3339 date-and-time string into a `DateTime` value. + /// + /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are + /// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a + /// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide + /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly + /// encountered variety of RFC 3339 formats. + /// + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing + /// values in a wide range of formats, only some of which represent actual date-and-time + /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are + /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 + /// values (or the other way around). + pub fn parse_from_rfc3339(s: &str) -> ParseResult> { + parse_rfc3339(s) + } + + /// Parses a string from a user-specified format into a `DateTime` value. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) + /// for a version that does not require a timezone in the to-be-parsed str. The returned + /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](crate::format::strftime) for supported format + /// sequences. + /// + /// # Example + /// + /// ```rust + /// use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone}; + /// + /// let dt = DateTime::parse_from_str("1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); + /// assert_eq!( + /// dt, + /// Ok(FixedOffset::east_opt(0) + /// .unwrap() + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(1983, 4, 13) + /// .unwrap() + /// .and_hms_milli_opt(12, 9, 14, 274) + /// .unwrap() + /// ) + /// .unwrap()) + /// ); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime() + } + + /// Parses a string from a user-specified format into a `DateTime` value, and a + /// slice with the remaining portion of the string. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_and_remainder`] for a version that does not + /// require a timezone in `s`. The returned [`DateTime`] value will have a [`FixedOffset`] + /// reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// let (datetime, remainder) = DateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 +0200 trailing text", + /// "%Y-%m-%d %H:%M:%S %z", + /// ) + /// .unwrap(); + /// assert_eq!( + /// datetime, + /// FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>( + s: &'a str, + fmt: &str, + ) -> ParseResult<(DateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime().map(|d| (d, remainder)) + } +} + +impl DateTime +where + Tz::Offset: fmt::Display, +{ + /// Formats the combined date and time with the specified formatting items. + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.overflowing_naive_local(); + DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) + } + + /// Formats the combined date and time per the specified format string. + /// + /// See the [`crate::format::strftime`] module for the supported escape sequences. + /// + /// # Example + /// ```rust + /// use chrono::prelude::*; + /// + /// let date_time: DateTime = Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap(); + /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); + /// assert_eq!(formatted, "02/04/2017 12:50"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Formats the combined date and time with the specified formatting items and locale. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.overflowing_naive_local(); + DelayedFormat::new_with_offset_and_locale( + Some(local.date()), + Some(local.time()), + &self.offset, + items, + locale, + ) + } + + /// Formats the combined date and time per the specified format string and + /// locale. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } +} + +impl Datelike for DateTime { + #[inline] + fn year(&self) -> i32 { + self.overflowing_naive_local().year() + } + #[inline] + fn month(&self) -> u32 { + self.overflowing_naive_local().month() + } + #[inline] + fn month0(&self) -> u32 { + self.overflowing_naive_local().month0() + } + #[inline] + fn day(&self) -> u32 { + self.overflowing_naive_local().day() + } + #[inline] + fn day0(&self) -> u32 { + self.overflowing_naive_local().day0() + } + #[inline] + fn ordinal(&self) -> u32 { + self.overflowing_naive_local().ordinal() + } + #[inline] + fn ordinal0(&self) -> u32 { + self.overflowing_naive_local().ordinal0() + } + #[inline] + fn weekday(&self) -> Weekday { + self.overflowing_naive_local().weekday() + } + #[inline] + fn iso_week(&self) -> IsoWeek { + self.overflowing_naive_local().iso_week() + } + + #[inline] + /// Makes a new `DateTime` with the year number changed, while keeping the same month and day. + /// + /// See also the [`NaiveDate::with_year`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless the year remains the same). + fn with_year(&self, year: i32) -> Option> { + map_local(self, |dt| match dt.year() == year { + true => Some(dt), + false => dt.with_year(year), + }) + } + + /// Makes a new `DateTime` with the month number (starting from 1) changed. + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// See also the [`NaiveDate::with_month`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_month(&self, month: u32) -> Option> { + map_local(self, |datetime| datetime.with_month(month)) + } + + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_month0(&self, month0: u32) -> Option> { + map_local(self, |datetime| datetime.with_month0(month0)) + } + + /// Makes a new `DateTime` with the day of month (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_day`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_day(&self, day: u32) -> Option> { + map_local(self, |datetime| datetime.with_day(day)) + } + + /// Makes a new `DateTime` with the day of month (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_day0(&self, day0: u32) -> Option> { + map_local(self, |datetime| datetime.with_day0(day0)) + } + + /// Makes a new `DateTime` with the day of year (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_ordinal`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal(ordinal)) + } + + /// Makes a new `DateTime` with the day of year (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) + } +} + +impl Timelike for DateTime { + #[inline] + fn hour(&self) -> u32 { + self.overflowing_naive_local().hour() + } + #[inline] + fn minute(&self) -> u32 { + self.overflowing_naive_local().minute() + } + #[inline] + fn second(&self) -> u32 { + self.overflowing_naive_local().second() + } + #[inline] + fn nanosecond(&self) -> u32 { + self.overflowing_naive_local().nanosecond() + } + + /// Makes a new `DateTime` with the hour number changed. + /// + /// See also the [`NaiveTime::with_hour`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `hour` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_hour(&self, hour: u32) -> Option> { + map_local(self, |datetime| datetime.with_hour(hour)) + } + + /// Makes a new `DateTime` with the minute number changed. + /// + /// See also the [`NaiveTime::with_minute`] method. + /// + /// # Errors + /// + /// - The value for `minute` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_minute(&self, min: u32) -> Option> { + map_local(self, |datetime| datetime.with_minute(min)) + } + + /// Makes a new `DateTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// See also the [`NaiveTime::with_second`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `second` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_second(&self, sec: u32) -> Option> { + map_local(self, |datetime| datetime.with_second(sec)) + } + + /// Makes a new `DateTime` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// As with the [`NaiveDateTime::nanosecond`] method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// See also the [`NaiveTime::with_nanosecond`] method. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option> { + map_local(self, |datetime| datetime.with_nanosecond(nano)) + } +} + +// We don't store a field with the `Tz` type, so it doesn't need to influence whether `DateTime` can +// be `Copy`. Implement it manually if the two types we do have are `Copy`. +impl Copy for DateTime +where + ::Offset: Copy, + NaiveDateTime: Copy, +{ +} + +impl PartialEq> for DateTime { + fn eq(&self, other: &DateTime) -> bool { + self.datetime == other.datetime + } +} + +impl Eq for DateTime {} + +impl PartialOrd> for DateTime { + /// Compare two DateTimes based on their true time, ignoring time zones + /// + /// # Example + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let earlier = Utc + /// .with_ymd_and_hms(2015, 5, 15, 2, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); + /// let later = Utc + /// .with_ymd_and_hms(2015, 5, 15, 3, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); + /// + /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); + /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); + /// + /// assert!(later > earlier); + /// ``` + fn partial_cmp(&self, other: &DateTime) -> Option { + self.datetime.partial_cmp(&other.datetime) + } +} + +impl Ord for DateTime { + fn cmp(&self, other: &DateTime) -> Ordering { + self.datetime.cmp(&other.datetime) + } +} + +impl hash::Hash for DateTime { + fn hash(&self, state: &mut H) { + self.datetime.hash(state) + } +} + +/// Add `TimeDelta` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn add(self, rhs: TimeDelta) -> DateTime { + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") + } +} + +/// Add `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn add(self, rhs: Duration) -> DateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") + } +} + +/// Add-assign `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for DateTime { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: TimeDelta) { + let datetime = + self.datetime.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime); + } +} + +/// Add-assign `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for DateTime { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: Duration) { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + *self += rhs; + } +} + +/// Add `FixedOffset` to the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn add(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = + self.naive_utc().checked_add_offset(rhs).expect("`DateTime + FixedOffset` overflowed"); + self + } +} + +/// Add `Months` to `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_add_months`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[track_caller] + fn add(self, rhs: Months) -> Self::Output { + self.checked_add_months(rhs).expect("`DateTime + Months` out of range") + } +} + +/// Subtract `TimeDelta` from `DateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn sub(self, rhs: TimeDelta) -> DateTime { + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") + } +} + +/// Subtract `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn sub(self, rhs: Duration) -> DateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `DateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for DateTime { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: TimeDelta) { + let datetime = + self.datetime.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime) + } +} + +/// Subtract-assign `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for DateTime { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: Duration) { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + *self -= rhs; + } +} + +/// Subtract `FixedOffset` from the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + #[track_caller] + fn sub(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = + self.naive_utc().checked_sub_offset(rhs).expect("`DateTime - FixedOffset` overflowed"); + self + } +} + +/// Subtract `Months` from `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`DateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_sub_months`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[track_caller] + fn sub(self, rhs: Months) -> Self::Output { + self.checked_sub_months(rhs).expect("`DateTime - Months` out of range") + } +} + +impl Sub> for DateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: DateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +impl Sub<&DateTime> for DateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: &DateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_add_days` to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[track_caller] + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`DateTime + Days` out of range") + } +} + +/// Subtract `Days` from `DateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[track_caller] + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`DateTime - Days` out of range") + } +} + +impl fmt::Debug for DateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.overflowing_naive_local().fmt(f)?; + self.offset.fmt(f) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for DateTime +where + Tz::Offset: defmt::Format, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}{}", self.overflowing_naive_local(), self.offset); + } +} + +// `fmt::Debug` is hand implemented for the `rkyv::Archive` variant of `DateTime` because +// deriving a trait recursively does not propagate trait defined associated types with their own +// constraints: +// In our case `<::Offset as Archive>::Archived` +// cannot be formatted using `{:?}` because it doesn't implement `Debug`. +// See below for further discussion: +// * https://github.com/rust-lang/rust/issues/26925 +// * https://github.com/rkyv/rkyv/issues/333 +// * https://github.com/dtolnay/syn/issues/370 +#[cfg(feature = "rkyv-validation")] +impl fmt::Debug for ArchivedDateTime +where + Tz: Archive, + ::Archived: fmt::Debug, + <::Offset as Archive>::Archived: fmt::Debug, + ::Offset: fmt::Debug + Archive, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ArchivedDateTime") + .field("datetime", &self.datetime) + .field("offset", &self.offset) + .finish() + } +} + +impl fmt::Display for DateTime +where + Tz::Offset: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.overflowing_naive_local().fmt(f)?; + f.write_char(' ')?; + self.offset.fmt(f) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Utc}; +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Local}; +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +#[cfg(feature = "clock")] +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Local)) + } +} + +#[cfg(feature = "std")] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { + Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), + Err(e) => { + // unlikely but should be handled + let dur = e.duration(); + let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); + if nsec == 0 { (-sec, 0) } else { (-sec - 1, 1_000_000_000 - nsec) } + } + }; + Utc.timestamp_opt(sec, nsec).unwrap() + } +} + +#[cfg(feature = "clock")] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + DateTime::::from(t).with_timezone(&Local) + } +} + +#[cfg(feature = "std")] +impl From> for SystemTime { + fn from(dt: DateTime) -> SystemTime { + let sec = dt.timestamp(); + let nsec = dt.timestamp_subsec_nanos(); + if sec < 0 { + // unlikely but should be handled + UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) + } else { + UNIX_EPOCH + Duration::new(sec as u64, nsec) + } + } +} + +#[cfg(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) +))] +impl From for DateTime { + fn from(date: js_sys::Date) -> DateTime { + DateTime::::from(&date) + } +} + +#[cfg(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) +))] +impl From<&js_sys::Date> for DateTime { + fn from(date: &js_sys::Date) -> DateTime { + Utc.timestamp_millis_opt(date.get_time() as i64).unwrap() + } +} + +#[cfg(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) +))] +impl From> for js_sys::Date { + /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, + /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 + /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. + fn from(date: DateTime) -> js_sys::Date { + let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); + js_sys::Date::new(&js_millis) + } +} + +// Note that implementation of Arbitrary cannot be simply derived for DateTime, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let datetime = NaiveDateTime::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(DateTime::from_naive_utc_and_offset(datetime, offset)) + } +} + +/// Number of days between January 1, 1970 and December 31, 1 BCE which we define to be day 0. +/// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388 +/// 1 day until January 1, 1601 1 +/// 369 years until January 1, 1970 369 * 365 = 134685 +/// of which floor(369 / 4) are leap years floor(369 / 4) = 92 +/// except for 1700, 1800 and 1900 -3 + +/// -------- +/// 719163 +pub(crate) const UNIX_EPOCH_DAY: i64 = 719_163; diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs new file mode 100644 index 0000000000..cde0f6238d --- /dev/null +++ b/src/datetime/serde.rs @@ -0,0 +1,1351 @@ +use core::fmt; +use serde::{de, ser}; + +use super::DateTime; +use crate::format::{SecondsFormat, write_rfc3339}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; + +#[doc(hidden)] +#[derive(Debug)] +pub struct SecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct NanoSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MicroSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MilliSecondsTimestampVisitor; + +/// Serialize to an RFC 3339 formatted string +/// +/// As an extension to RFC 3339 this can serialize `DateTime`s outside the range of 0-9999 years +/// using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate serializations. +impl ser::Serialize for DateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatIso8601<'a, Tz: TimeZone> { + inner: &'a DateTime, + } + + impl fmt::Display for FormatIso8601<'_, Tz> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let naive = self.inner.naive_local(); + let offset = self.inner.offset.fix(); + write_rfc3339(f, naive, offset, SecondsFormat::AutoSi, true) + } + } + + serializer.collect_str(&FormatIso8601 { inner: self }) + } +} + +struct DateTimeVisitor; + +impl de::Visitor<'_> for DateTimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an RFC 3339 formatted date and time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// If the value contains an offset from UTC that is not zero, the value will be converted to UTC. +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// The value will remain the same instant in UTC, but the offset will be recalculated to match +/// that of the `Local` platform time zone. +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +#[cfg(feature = "clock")] +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) + } +} + +/// Ser/de to/from timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::NanoSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) + } + + /// Deserialize a [`DateTime`] from a nanosecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_999).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor) + } + + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(DateTime::from_timestamp_nanos(value)) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::{DateTime, Utc}; + + use super::NanoSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::MicroSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_micros()) + } + + /// Deserialize a `DateTime` from a microsecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_000).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor) + } + + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, + ) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MicroSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a microsecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::MilliSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_millis()) + } + + /// Deserialize a `DateTime` from a millisecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_000_000).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) + } + + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MilliSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// #[serde(untagged)] + /// enum E { + /// V(T), + /// } + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// struct S { + /// #[serde(default, deserialize_with = "from_milli_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp_opt(1526522699, 918000000).unwrap()) })); + /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; + /// assert_eq!(s, E::V(S { time: None })); + /// let t: E = serde_json::from_str(r#"{}"#)?; + /// assert_eq!(t, E::V(S { time: None })); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: DateTime, +/// } +/// +/// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::SecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap() }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp()) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor) + } + + impl de::Visitor<'_> for SecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_secs(value).ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + DateTime::from_timestamp_secs(value as i64).ok_or_else(|| invalid_ts(value)) + } + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::SecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()) }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a seconds timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "clock")] + use crate::Local; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use core::fmt; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), + Some(r#""2014-07-24T12:34:06Z""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3660) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3650) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. + // In this case `+01:00:50` becomes `+01:01` + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + } + + #[test] + fn test_serde_deserialize() { + // should check against the offset as well (the normal DateTime comparison will ignore them) + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + let dt: Option> = serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + let dt: Option> = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + + let dt: Option> = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() + )) + ); + let dt: Option> = + serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(60 * 60 + 23 * 60) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) + .unwrap() + )) + ); + + // we don't know the exact local offset but we can check that + // the conversion didn't change the instant itself + #[cfg(feature = "clock")] + { + let dt: DateTime = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + + let dt: DateTime = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#) + .expect("local should parse with offset"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + } + + assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!( + serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err() + ); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: DateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset(), decoded.offset()); + } + + #[test] + fn test_serde_no_offset_debug() { + use crate::{MappedLocalTime, NaiveDate, NaiveDateTime, Offset}; + use core::fmt::Debug; + + #[derive(Clone)] + struct TestTimeZone; + impl Debug for TestTimeZone { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TEST") + } + } + impl TimeZone for TestTimeZone { + type Offset = TestTimeZone; + fn from_offset(_state: &TestTimeZone) -> TestTimeZone { + TestTimeZone + } + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) + } + fn offset_from_local_datetime( + &self, + _local: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) + } + fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { + TestTimeZone + } + fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> TestTimeZone { + TestTimeZone + } + } + impl Offset for TestTimeZone { + fn fix(&self) -> FixedOffset { + FixedOffset::east_opt(15 * 60 * 60).unwrap() + } + } + + let tz = TestTimeZone; + assert_eq!(format!("{:?}", tz), "TEST"); + + let dt = tz.with_ymd_and_hms(2023, 4, 24, 21, 10, 33).unwrap(); + let encoded = serde_json::to_string(&dt).unwrap(); + dbg!(&encoded); + let decoded: DateTime = serde_json::from_str(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset().fix(), *decoded.offset()); + } +} diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs new file mode 100644 index 0000000000..1da69d9c7c --- /dev/null +++ b/src/datetime/tests.rs @@ -0,0 +1,1912 @@ +use super::DateTime; +use crate::naive::{NaiveDate, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::{Datelike, Days, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; + +#[derive(Clone)] +struct DstTester; + +impl DstTester { + fn winter_offset() -> FixedOffset { + FixedOffset::east_opt(8 * 60 * 60).unwrap() + } + fn summer_offset() -> FixedOffset { + FixedOffset::east_opt(9 * 60 * 60).unwrap() + } + + const TO_WINTER_MONTH_DAY: (u32, u32) = (4, 15); + const TO_SUMMER_MONTH_DAY: (u32, u32) = (9, 15); + + fn transition_start_local() -> NaiveTime { + NaiveTime::from_hms_opt(2, 0, 0).unwrap() + } +} + +impl TimeZone for DstTester { + type Offset = FixedOffset; + + fn from_offset(_: &Self::Offset) -> Self { + DstTester + } + + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { + unimplemented!() + } + + fn offset_from_local_datetime( + &self, + local: &NaiveDateTime, + ) -> crate::MappedLocalTime { + let local_to_winter_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_winter_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() - TimeDelta::try_hours(1).unwrap()); + + let local_to_summer_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_summer_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap()); + + if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { + MappedLocalTime::Single(DstTester::summer_offset()) + } else if *local >= local_to_winter_transition_start + && *local < local_to_summer_transition_start + { + MappedLocalTime::Single(DstTester::winter_offset()) + } else if *local >= local_to_winter_transition_end + && *local < local_to_winter_transition_start + { + MappedLocalTime::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) + } else if *local >= local_to_summer_transition_start + && *local < local_to_summer_transition_end + { + MappedLocalTime::None + } else { + panic!("Unexpected local time {local}") + } + } + + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset { + let utc_to_winter_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::summer_offset(); + + let utc_to_summer_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::winter_offset(); + + if *utc < utc_to_winter_transition || *utc >= utc_to_summer_transition { + DstTester::summer_offset() + } else if *utc >= utc_to_winter_transition && *utc < utc_to_summer_transition { + DstTester::winter_offset() + } else { + panic!("Unexpected utc time {utc}") + } + } +} + +#[test] +fn test_datetime_from_timestamp_millis() { + let valid_map = [ + (1662921288000, "2022-09-11 18:34:48.000000000"), + (1662921288123, "2022-09-11 18:34:48.123000000"), + (1662921287890, "2022-09-11 18:34:47.890000000"), + (-2208936075000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000, "1973-10-17 18:36:57.000000000"), + (1234567890000, "2009-02-13 23:31:30.000000000"), + (2034061609000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_millis, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis).unwrap(); + assert_eq!(timestamp_millis, datetime.timestamp_millis()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_millis in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis); + assert!(datetime.is_none()); + } + + // Test that the result of `from_timestamp_millis` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().cloned() { + assert_eq!( + DateTime::from_timestamp_millis(secs * 1000), + DateTime::from_timestamp_secs(secs) + ); + } +} + +#[test] +fn test_datetime_from_timestamp_micros() { + let valid_map = [ + (1662921288000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456, "2022-09-11 18:34:48.123456000"), + (1662921287890000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_micros, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros).unwrap(); + assert_eq!(timestamp_micros, datetime.timestamp_micros()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_micros in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros); + assert!(datetime.is_none()); + } + + // Test that the result of `TimeZone::timestamp_micros` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + DateTime::from_timestamp_micros(secs * 1_000_000), + DateTime::from_timestamp_secs(secs) + ); + } +} + +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_nanos(timestamp_nanos); + assert_eq!(timestamp_nanos, datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + + // Test that the result of `TimeZone::timestamp_nanos` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + Some(DateTime::from_timestamp_nanos(secs * 1_000_000_000)), + DateTime::from_timestamp_secs(secs) + ); + } +} + +#[test] +fn test_datetime_from_timestamp_secs() { + let valid = [-2208936075, 0, 119731017, 1234567890, 2034061609]; + + for timestamp_secs in valid.iter().copied() { + let datetime = DateTime::from_timestamp_secs(timestamp_secs).unwrap(); + assert_eq!(timestamp_secs, datetime.timestamp()); + assert_eq!(DateTime::from_timestamp(timestamp_secs, 0).unwrap(), datetime); + } +} + +#[test] +fn test_datetime_from_timestamp() { + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().and_utc() + }; + assert_eq!(DateTime::from_timestamp_secs(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(DateTime::from_timestamp_secs(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(DateTime::from_timestamp_secs(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(DateTime::from_timestamp_secs(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(DateTime::from_timestamp_secs(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(DateTime::from_timestamp_secs(i64::MIN), None); + assert_eq!(DateTime::from_timestamp_secs(i64::MAX), None); +} + +#[test] +fn test_datetime_timestamp() { + let to_timestamp = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_opt(h, n, s) + .unwrap() + .and_utc() + .timestamp() + }; + assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); + assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); + assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); +} + +#[test] +fn test_nanosecond_range() { + const A_BILLION: i64 = 1_000_000_000; + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Just beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::try_milliseconds(300).unwrap(); + assert!(beyond_max.timestamp_nanos_opt().is_none()); + + // Far beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + Days::new(365); + assert!(beyond_max.timestamp_nanos_opt().is_none()); +} + +#[test] +fn test_datetime_add_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-04-11 07:08:09 +09:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-04-16 07:08:09 +08:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-09-11 07:08:09 +08:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-09-16 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_add_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 +09:00" + ); +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(milli * 1_000_000) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "alloc")] +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(micro * 1000) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "alloc")] +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(nano) + .unwrap() +} + +// local helper function to easily create a DateTime +#[cfg(feature = "alloc")] +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: u32, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(milli * 1_000_000) + .unwrap() +} + +#[test] +fn test_datetime_offset() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 UTC" + ); + assert_eq!( + format!("{}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 -04:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 +09:00" + ); + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09+09:00" + ); + + // edge cases + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00+09:00" + ); + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59+09:00" + ); + + let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); + assert_eq!( + dt + TimeDelta::try_seconds(3600 + 60 + 1).unwrap(), + Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() + ); + assert_eq!( + dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), + TimeDelta::try_seconds(-7 * 3600 - 3 * 60 - 3).unwrap() + ); + + assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); + assert_eq!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), edt); + assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); +} + +#[test] +#[allow(clippy::needless_borrow, clippy::op_ref)] +fn signed_duration_since_autoref() { + let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap(); + let diff1 = dt1.signed_duration_since(dt2); // Copy/consume + #[allow(clippy::needless_borrows_for_generic_args)] + let diff2 = dt2.signed_duration_since(&dt1); // Take by reference + assert_eq!(diff1, -diff2); + + let diff1 = dt1 - &dt2; // We can choose to subtract rhs by reference + let diff2 = dt2 - dt1; // Or consume rhs + assert_eq!(diff1, -diff2); +} + +#[test] +fn test_datetime_date_and_time() { + let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(7, 8, 9).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2014, 5, 6).unwrap()); + + let tz = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2016, 5, 4, 3, 2, 1).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(3, 2, 1).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2016, 5, 4).unwrap()); + + let tz = FixedOffset::west_opt(13 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(12, 34, 56).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2017, 8, 9).unwrap()); + + let utc_d = Utc.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); + assert!(utc_d < d); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_with_timezone() { + let local_now = Local::now(); + let utc_now = local_now.with_timezone(&Utc); + let local_now2 = utc_now.with_timezone(&Local); + assert_eq!(local_now, local_now2); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_datetime_rfc2822() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0000" + ); + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 1, 23, 16, 9).unwrap().to_rfc2822(), + "Sun, 1 Feb 2015 23:16:09 +0000" + ); + // timezone +05 + assert_eq!( + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0500" + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap() + ) + .unwrap()) + ); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap()) + ); + // seconds 60 + assert_eq!( + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + ); + assert_eq!( + ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" + ), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) + ); + + // bad year + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err()); + // full name day of week + assert!(DateTime::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000").is_err()); + // full name day of week + assert!(DateTime::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000").is_err()); + // wrong day of week separator '.' + assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err()); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_datetime_rfc3339() { + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + // timezone +05 + assert_eq!( + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + assert_eq!( + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + ); + + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567)) + ); + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err()); + assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err()); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_rfc3339_opts() { + use crate::SecondsFormat::*; + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); + assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); + assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); + assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); + + let ut = dt.naive_utc().and_utc(); + assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); + assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); + assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); + assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); + assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); + assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); + assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); +} + +#[test] +#[should_panic] +#[cfg(feature = "alloc")] +fn test_rfc3339_opts_nonexhaustive() { + use crate::SecondsFormat; + let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); + let _ = dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); +} + +#[test] +fn test_datetime_from_str() { + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Utc".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + assert!("2015-02-18T23:16:9.15øøø".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12 12:12:12Z", + "2012-12-12t12:12:12Z", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {s:?}"); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{s}` has failed: {e}"), + }; + let s_ = format!("{d:?}"); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") + } + }; + assert!( + d == d_, + "`{s}` is parsed into `{d:?}`, but reparsed result `{d_:?}` does not match" + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {s:?}"); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_parse_from_str() { + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); + assert_eq!( + DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) + ); // ignore offset + assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset + assert!( + DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); + assert_eq!( + DateTime::parse_from_str("0", "%s").unwrap(), + DateTime::from_timestamp(0, 0).unwrap().fixed_offset() + ); + + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); + let parse = DateTime::parse_from_str; + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); + // bad timezone data? + assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); + // bad timezone data + assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); + + // + // %z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); + // trailing newline after timezone + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z "), Ok(dt)); + // trailing colon + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); + // trailing colon with space + assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); + // trailing colon, mismatch space + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); + + // + // %:z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:"), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + // timezone data hs too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); + // timezone data hs too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); + + // + // %::z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + // mismatching colon expectations + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); + // mismatching colons and spaces + assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); + // mismatching colons expectations + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); + // mismatching colons expectations mid-string + assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); + // mismatching colons expectations, before end + assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); + + // + // %:::z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); + + // + // %::::z + // + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); + + // + // %#z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + // timezone with partial minutes adjacent hours + assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + // bad timezone data + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); + // bad timezone data (partial minutes) + assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); + // bad timezone data (partial minutes) mid-string + assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); + // bad timezone data + assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); + // timezone data ambiguous with hours + assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); +} + +#[test] +fn test_to_string_round_trip() { + let dt = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); + let _dt: DateTime = dt.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap()); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap()); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_to_string_round_trip_with_local() { + let ndt = Local::now(); + let _dt: DateTime = ndt.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_format_with_local() { + // if we are not around the year boundary, local and UTC date should have the same year + let dt = Local::now().with_month(5).unwrap(); + assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); +} + +#[test] +fn test_datetime_is_send_and_copy() { + #[derive(Clone)] + struct Tz { + _not_send: *const i32, + } + impl TimeZone for Tz { + type Offset = Off; + + fn from_offset(_: &Self::Offset) -> Self { + unimplemented!() + } + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_local_datetime( + &self, + _: &NaiveDateTime, + ) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + fn offset_from_utc_datetime(&self, _: &NaiveDateTime) -> Self::Offset { + unimplemented!() + } + } + + #[derive(Copy, Clone, Debug)] + struct Off(()); + impl Offset for Off { + fn fix(&self) -> FixedOffset { + unimplemented!() + } + } + + fn _assert_send_copy() {} + // `DateTime` is `Send + Copy` if the offset is. + _assert_send_copy::>(); +} + +#[test] +fn test_subsecond_part() { + let datetime = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 7, 8) + .unwrap() + .and_hms_nano_opt(9, 10, 11, 1234567) + .unwrap(), + ) + .unwrap(); + + assert_eq!(1, datetime.timestamp_subsec_millis()); + assert_eq!(1234, datetime.timestamp_subsec_micros()); + assert_eq!(1234567, datetime.timestamp_subsec_nanos()); +} + +// Some targets, such as `wasm32-wasi`, have a problematic definition of `SystemTime`, such as an +// `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`). +// See https://github.com/rust-lang/rust/issues/44394. +#[test] +#[cfg(all(feature = "std", not(all(target_family = "wasm", target_os = "wasi"))))] +fn test_from_system_time() { + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + let nanos = 999_999_000; + + let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); + + // SystemTime -> DateTime + assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); + assert_eq!( + DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() + ); + assert_eq!( + DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() + ); + + // DateTime -> SystemTime + assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); + assert_eq!( + SystemTime::from( + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() + ), + UNIX_EPOCH + Duration::new(999_999_999, nanos) + ); + assert_eq!( + SystemTime::from( + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() + ), + UNIX_EPOCH - Duration::new(999_999_999, nanos) + ); + + // DateTime -> SystemTime (via `with_timezone`) + #[cfg(feature = "clock")] + { + assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); + } + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), + UNIX_EPOCH + ); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), + UNIX_EPOCH + ); +} + +#[test] +#[allow(deprecated)] +fn test_datetime_from_local() { + // 2000-01-12T02:00:00Z + let naivedatetime_utc = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + + // 2000-01-12T10:00:00+8:00:00 + let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let naivedatetime_east = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); + let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); + + // 2000-01-11T19:00:00-7:00:00 + let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); + let naivedatetime_west = + NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); + let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); + + assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); + assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_before_windows_api_limits() { + // dt corresponds to `FILETIME = 147221225472` from issue 651. + // (https://github.com/chronotope/chrono/issues/651) + // This used to fail on Windows for timezones with an offset of -5:00 or greater. + // The API limits years to 1601..=30827. + let dt = NaiveDate::from_ymd_opt(1601, 1, 1).unwrap().and_hms_milli_opt(4, 5, 22, 122).unwrap(); + let local_dt = Local.from_utc_datetime(&dt); + dbg!(local_dt); +} + +#[test] +#[cfg(feature = "clock")] +fn test_years_elapsed() { + // A bit more than 1 year + let one_year_ago = Utc::now().date_naive() - Days::new(400); + // A bit more than 2 years + let two_year_ago = Utc::now().date_naive() - Days::new(750); + + assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); + + // If the given DateTime is later than now, the function will always return 0. + let future = Utc::now().date_naive() + Days(100); + assert_eq!(Utc::now().date_naive().years_since(future), None); +} + +#[test] +fn test_datetime_add_assign() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let datetime = naivedatetime.and_utc(); + let mut datetime_add = datetime; + + datetime_add += TimeDelta::try_seconds(60).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_add_assign_local() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_add = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_add += TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_days(i).unwrap()) + } +} + +#[test] +fn test_datetime_sub_assign() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(); + let datetime = naivedatetime.and_utc(); + let mut datetime_sub = datetime; + + datetime_sub -= TimeDelta::try_minutes(90).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); +} + +#[test] +fn test_min_max_getters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(format!("{beyond_min:?}"), "-262144-12-31T22:00:00-02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_min.to_rfc2822(), ""); + #[cfg(feature = "alloc")] + assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); + #[cfg(feature = "alloc")] + assert_eq!( + beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), + "-262144-12-31T22:00:00-02:00" + ); + assert_eq!(beyond_min.year(), -262144); + assert_eq!(beyond_min.month(), 12); + assert_eq!(beyond_min.month0(), 11); + assert_eq!(beyond_min.day(), 31); + assert_eq!(beyond_min.day0(), 30); + assert_eq!(beyond_min.ordinal(), 366); + assert_eq!(beyond_min.ordinal0(), 365); + assert_eq!(beyond_min.weekday(), Weekday::Wed); + assert_eq!(beyond_min.iso_week().year(), -262143); + assert_eq!(beyond_min.iso_week().week(), 1); + assert_eq!(beyond_min.hour(), 22); + assert_eq!(beyond_min.minute(), 0); + assert_eq!(beyond_min.second(), 0); + assert_eq!(beyond_min.nanosecond(), 0); + + assert_eq!(format!("{beyond_max:?}"), "+262143-01-01T01:59:59.999999999+02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_max.to_rfc2822(), ""); + #[cfg(feature = "alloc")] + assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); + #[cfg(feature = "alloc")] + assert_eq!( + beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), + "+262143-01-01T01:59:59.999999999+02:00" + ); + assert_eq!(beyond_max.year(), 262143); + assert_eq!(beyond_max.month(), 1); + assert_eq!(beyond_max.month0(), 0); + assert_eq!(beyond_max.day(), 1); + assert_eq!(beyond_max.day0(), 0); + assert_eq!(beyond_max.ordinal(), 1); + assert_eq!(beyond_max.ordinal0(), 0); + assert_eq!(beyond_max.weekday(), Weekday::Tue); + assert_eq!(beyond_max.iso_week().year(), 262143); + assert_eq!(beyond_max.iso_week().week(), 1); + assert_eq!(beyond_max.hour(), 1); + assert_eq!(beyond_max.minute(), 59); + assert_eq!(beyond_max.second(), 59); + assert_eq!(beyond_max.nanosecond(), 999_999_999); +} + +#[test] +fn test_min_max_setters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(beyond_min.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_min.with_year(beyond_min.year()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(beyond_min.month()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(3), None); + assert_eq!(beyond_min.with_month0(beyond_min.month0()), Some(beyond_min)); + assert_eq!(beyond_min.with_month0(3), None); + assert_eq!(beyond_min.with_day(beyond_min.day()), Some(beyond_min)); + assert_eq!(beyond_min.with_day(15), None); + assert_eq!(beyond_min.with_day0(beyond_min.day0()), Some(beyond_min)); + assert_eq!(beyond_min.with_day0(15), None); + assert_eq!(beyond_min.with_ordinal(beyond_min.ordinal()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal(200), None); + assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal0(200), None); + assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); + assert_eq!( + beyond_min.with_hour(23), + beyond_min.checked_add_signed(TimeDelta::try_hours(1).unwrap()) + ); + assert_eq!(beyond_min.with_hour(5), None); + assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); + assert_eq!(beyond_min.with_second(0), Some(beyond_min)); + assert_eq!(beyond_min.with_nanosecond(0), Some(beyond_min)); + + assert_eq!(beyond_max.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_max.with_year(beyond_max.year()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(beyond_max.month()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(3), None); + assert_eq!(beyond_max.with_month0(beyond_max.month0()), Some(beyond_max)); + assert_eq!(beyond_max.with_month0(3), None); + assert_eq!(beyond_max.with_day(beyond_max.day()), Some(beyond_max)); + assert_eq!(beyond_max.with_day(15), None); + assert_eq!(beyond_max.with_day0(beyond_max.day0()), Some(beyond_max)); + assert_eq!(beyond_max.with_day0(15), None); + assert_eq!(beyond_max.with_ordinal(beyond_max.ordinal()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal(200), None); + assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal0(200), None); + assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); + assert_eq!( + beyond_max.with_hour(0), + beyond_max.checked_sub_signed(TimeDelta::try_hours(1).unwrap()) + ); + assert_eq!(beyond_max.with_hour(5), None); + assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); + assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); + assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); +} + +#[test] +fn test_min_max_add_days() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_days(Days::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_days(Days::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Days(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_days(Days::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_days(Days::new(1)), None); + + assert_eq!(beyond_max.checked_add_days(Days::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_days(Days::new(1)), None); + assert_eq!(beyond_max.checked_sub_days(Days::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_days(Days::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Days(1)).and_time(max_time))) + ); +} + +#[test] +fn test_min_max_add_months() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_months(Months::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_months(Months::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Months(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_months(Months::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_months(Months::new(1)), None); + + assert_eq!(beyond_max.checked_add_months(Months::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_months(Months::new(1)), None); + assert_eq!(beyond_max.checked_sub_months(Months::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_months(Months::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Months(1)).and_time(max_time))) + ); +} + +#[test] +#[should_panic] +fn test_local_beyond_min_datetime() { + let min = FixedOffset::west_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MIN); + let _ = min.naive_local(); +} + +#[test] +#[should_panic] +fn test_local_beyond_max_datetime() { + let max = FixedOffset::east_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MAX); + let _ = max.naive_local(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_sub_assign_local() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_sub -= TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_days(i).unwrap()) + } +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + let same = utc_dt + Duration::ZERO; + assert_eq!(utc_dt, same); + + utc_dt += Duration::new(3600, 0); + assert_eq!(utc_dt, Utc.with_ymd_and_hms(2023, 8, 29, 12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_local_from_preserves_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let offset = datetime.offset().fix(); + + let datetime_fixed: DateTime = datetime.into(); + assert_eq!(&offset, datetime_fixed.offset()); + assert_eq!(datetime.fixed_offset(), datetime_fixed); +} + +#[test] +fn test_datetime_fixed_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Utc.from_utc_datetime(&naivedatetime); + let fixed_utc = FixedOffset::east_opt(0).unwrap(); + assert_eq!(datetime.fixed_offset(), fixed_utc.from_local_datetime(&naivedatetime).unwrap()); + + let fixed_offset = FixedOffset::east_opt(3600).unwrap(); + let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap(); + assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); +} + +#[test] +fn test_datetime_to_utc() { + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 2, 22, 23, 24, 25).unwrap(); + let dt_utc: DateTime = dt.to_utc(); + assert_eq!(dt, dt_utc); +} + +#[test] +fn test_add_sub_months() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap()); +} + +#[test] +fn test_auto_conversion() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) + .unwrap() + .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) + .unwrap(); + let utc_dt2: DateTime = cdt_dt.into(); + assert_eq!(utc_dt, utc_dt2); +} + +#[test] +#[cfg(feature = "clock")] +#[allow(deprecated)] +fn test_test_deprecated_from_offset() { + let now = Local::now(); + let naive = now.naive_local(); + let utc = now.naive_utc(); + let offset: FixedOffset = *now.offset(); + + assert_eq!(DateTime::::from_local(naive, offset), now); + assert_eq!(DateTime::::from_utc(utc, offset), now); +} + +#[test] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +fn locale_decimal_point() { + use crate::Locale::{ar_SY, nl_NL}; + let dt = + Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap(); + + assert_eq!(dt.format_localized("%T%.f", nl_NL).to_string(), "18:58:00,123456780"); + assert_eq!(dt.format_localized("%T%.3f", nl_NL).to_string(), "18:58:00,123"); + assert_eq!(dt.format_localized("%T%.6f", nl_NL).to_string(), "18:58:00,123456"); + assert_eq!(dt.format_localized("%T%.9f", nl_NL).to_string(), "18:58:00,123456780"); + + assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00.123456780"); + assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00.123"); + assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); + assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); +} + +/// This is an extended test for . +#[test] +fn nano_roundrip() { + const BILLION: i64 = 1_000_000_000; + + for nanos in [ + i64::MIN, + i64::MIN + 1, + i64::MIN + 2, + i64::MIN + BILLION - 1, + i64::MIN + BILLION, + i64::MIN + BILLION + 1, + -BILLION - 1, + -BILLION, + -BILLION + 1, + 0, + BILLION - 1, + BILLION, + BILLION + 1, + i64::MAX - BILLION - 1, + i64::MAX - BILLION, + i64::MAX - BILLION + 1, + i64::MAX - 2, + i64::MAX - 1, + i64::MAX, + ] { + println!("nanos: {nanos}"); + let dt = Utc.timestamp_nanos(nanos); + let nanos2 = dt.timestamp_nanos_opt().expect("value roundtrips"); + assert_eq!(nanos, nanos2); + } +} diff --git a/src/div.rs b/src/div.rs deleted file mode 100644 index 64b8e4bce4..0000000000 --- a/src/div.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This is a part of Chrono. -// Portions Copyright 2013-2014 The Rust Project Developers. -// See README.md and LICENSE.txt for details. - -//! Integer division utilities. (Shamelessly copied from [num](https://github.com/rust-lang/num/)) - -// Algorithm from [Daan Leijen. _Division and Modulus for Computer Scientists_, -// December 2001](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf) - -pub use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor}; - -#[cfg(test)] -mod tests { - use super::{div_mod_floor, mod_floor}; - - #[test] - fn test_mod_floor() { - assert_eq!(mod_floor(8, 3), 2); - assert_eq!(mod_floor(8, -3), -1); - assert_eq!(mod_floor(-8, 3), 1); - assert_eq!(mod_floor(-8, -3), -2); - - assert_eq!(mod_floor(1, 2), 1); - assert_eq!(mod_floor(1, -2), -1); - assert_eq!(mod_floor(-1, 2), 1); - assert_eq!(mod_floor(-1, -2), -1); - } - - #[test] - fn test_div_mod_floor() { - assert_eq!(div_mod_floor(8, 3), (2, 2)); - assert_eq!(div_mod_floor(8, -3), (-3, -1)); - assert_eq!(div_mod_floor(-8, 3), (-3, 1)); - assert_eq!(div_mod_floor(-8, -3), (2, -2)); - - assert_eq!(div_mod_floor(1, 2), (0, 1)); - assert_eq!(div_mod_floor(1, -2), (-1, -1)); - assert_eq!(div_mod_floor(-1, 2), (-1, 1)); - assert_eq!(div_mod_floor(-1, -2), (0, -1)); - } -} diff --git a/src/format/formatting.rs b/src/format/formatting.rs new file mode 100644 index 0000000000..b5ef253a57 --- /dev/null +++ b/src/format/formatting.rs @@ -0,0 +1,990 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! Date and time formatting routines. + +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::string::{String, ToString}; +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +#[cfg(feature = "alloc")] +use core::fmt::Display; +use core::fmt::{self, Write}; + +#[cfg(feature = "alloc")] +use crate::offset::Offset; +#[cfg(any(feature = "alloc", feature = "serde"))] +use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; +#[cfg(feature = "alloc")] +use crate::{NaiveDate, NaiveTime, Weekday}; + +#[cfg(feature = "alloc")] +use super::locales; +#[cfg(any(feature = "alloc", feature = "serde"))] +use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; +#[cfg(feature = "alloc")] +use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; +#[cfg(feature = "alloc")] +use locales::*; + +/// A *temporary* object which can be used as an argument to `format!` or others. +/// This is normally constructed via `format` methods of each date and time type. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct DelayedFormat { + /// The date view, if any. + date: Option, + /// The time view, if any. + time: Option, + /// The name and local-to-UTC difference for the offset (timezone), if any. + off: Option<(String, FixedOffset)>, + /// An iterator returning formatting items. + items: I, + /// Locale used for text. + /// ZST if the `unstable-locales` feature is not enabled. + locale: Locale, +} + +#[cfg(feature = "alloc")] +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { + /// Makes a new `DelayedFormat` value out of local date and time. + #[must_use] + pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale: default_locale() } + } + + /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + #[must_use] + pub fn new_with_offset( + date: Option, + time: Option, + offset: &Off, + items: I, + ) -> DelayedFormat + where + Off: Offset + Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: default_locale() } + } + + /// Makes a new `DelayedFormat` value out of local date and time and locale. + #[cfg(feature = "unstable-locales")] + #[must_use] + pub fn new_with_locale( + date: Option, + time: Option, + items: I, + locale: Locale, + ) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale } + } + + /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. + #[cfg(feature = "unstable-locales")] + #[must_use] + pub fn new_with_offset_and_locale( + date: Option, + time: Option, + offset: &Off, + items: I, + locale: Locale, + ) -> DelayedFormat + where + Off: Offset + Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale } + } + + /// Formats `DelayedFormat` into a `core::fmt::Write` instance. + /// # Errors + /// This function returns a `core::fmt::Error` if formatting into the `core::fmt::Write` instance fails. + /// + /// # Example + /// ### Writing to a String + /// ``` + /// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + /// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + /// let mut buffer = String::new(); + /// let _ = df.write_to(&mut buffer); + /// ``` + pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> fmt::Result { + for item in self.items.clone() { + match *item.borrow() { + Item::Literal(s) | Item::Space(s) => w.write_str(s), + #[cfg(feature = "alloc")] + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), + Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), + Item::Fixed(ref spec) => self.format_fixed(w, spec), + Item::Error => Err(fmt::Error), + }?; + } + Ok(()) + } + + #[cfg(feature = "alloc")] + fn format_numeric( + &self, + w: &mut (impl Write + ?Sized), + spec: &Numeric, + pad: Pad, + ) -> fmt::Result { + use self::Numeric::*; + + fn write_one(w: &mut (impl Write + ?Sized), v: u8) -> fmt::Result { + w.write_char((b'0' + v) as char) + } + + fn write_two(w: &mut (impl Write + ?Sized), v: u8, pad: Pad) -> fmt::Result { + let ones = b'0' + v % 10; + match (v / 10, pad) { + (0, Pad::None) => {} + (0, Pad::Space) => w.write_char(' ')?, + (tens, _) => w.write_char((b'0' + tens) as char)?, + } + w.write_char(ones as char) + } + + #[inline] + fn write_year(w: &mut (impl Write + ?Sized), year: i32, pad: Pad) -> fmt::Result { + if (1000..=9999).contains(&year) { + // fast path + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8) + } else { + write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year)) + } + } + + fn write_n( + w: &mut (impl Write + ?Sized), + n: usize, + v: i64, + pad: Pad, + always_sign: bool, + ) -> fmt::Result { + if always_sign { + match pad { + Pad::None => write!(w, "{v:+}"), + Pad::Zero => write!(w, "{:+01$}", v, n + 1), + Pad::Space => write!(w, "{:+1$}", v, n + 1), + } + } else { + match pad { + Pad::None => write!(w, "{v}"), + Pad::Zero => write!(w, "{v:0n$}"), + Pad::Space => write!(w, "{v:n$}"), + } + } + } + + match (spec, self.date, self.time) { + (Year, Some(d), _) => write_year(w, d.year(), pad), + (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad), + (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad), + (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad), + (IsoYearDiv100, Some(d), _) => { + write_two(w, d.iso_week().year().div_euclid(100) as u8, pad) + } + (IsoYearMod100, Some(d), _) => { + write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad) + } + (Quarter, Some(d), _) => write_one(w, d.quarter() as u8), + (Month, Some(d), _) => write_two(w, d.month() as u8, pad), + (Day, Some(d), _) => write_two(w, d.day() as u8, pad), + (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad), + (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad), + (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad), + (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8), + (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8), + (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false), + (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad), + (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad), + (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad), + (Second, _, Some(t)) => { + write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad) + } + (Nanosecond, _, Some(t)) => { + write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false) + } + (Timestamp, Some(d), Some(t)) => { + let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc())); + let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0); + write_n(w, 9, timestamp, pad, false) + } + (Internal(_), _, _) => Ok(()), // for future expansion + _ => Err(fmt::Error), // insufficient arguments for given format + } + } + + #[cfg(feature = "alloc")] + fn format_fixed(&self, w: &mut (impl Write + ?Sized), spec: &Fixed) -> fmt::Result { + use Fixed::*; + use InternalInternal::*; + + match (spec, self.date, self.time, self.off.as_ref()) { + (ShortMonthName, Some(d), _, _) => { + w.write_str(short_months(self.locale)[d.month0() as usize]) + } + (LongMonthName, Some(d), _, _) => { + w.write_str(long_months(self.locale)[d.month0() as usize]) + } + (ShortWeekdayName, Some(d), _, _) => w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + ), + (LongWeekdayName, Some(d), _, _) => { + w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize]) + } + (LowerAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? + } + Ok(()) + } + (UpperAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + w.write_str(ampm) + } + (Nanosecond, _, Some(t), _) => { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else { + w.write_str(decimal_point(self.locale))?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{nano:09}") + } + } + } + (Nanosecond3, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000) + } + (Nanosecond6, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) + } + (Nanosecond9, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000) + } + (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) + } + (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{tz_name}"), + (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetDoubleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetTripleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (RFC2822, Some(d), Some(t), Some((_, off))) => { + write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off) + } + (RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + *off, + SecondsFormat::AutoSi, + false, + ), + _ => Err(fmt::Error), // insufficient arguments for given format + } + } +} + +#[cfg(feature = "alloc")] +impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut result = String::new(); + self.write_to(&mut result)?; + f.pad(&result) + } +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(feature = "alloc")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] +pub fn format<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items, + locale: default_locale(), + } + .fmt(w) +} + +/// Formats single formatting item +#[cfg(feature = "alloc")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] +pub fn format_item( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, +) -> fmt::Result { + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items: [item].into_iter(), + locale: default_locale(), + } + .fmt(w) +} + +#[cfg(any(feature = "alloc", feature = "serde"))] +impl OffsetFormat { + /// Writes an offset from UTC with the format defined by `self`. + fn format(&self, w: &mut (impl Write + ?Sized), off: FixedOffset) -> fmt::Result { + let off = off.local_minus_utc(); + if self.allow_zulu && off == 0 { + w.write_char('Z')?; + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + + let hours; + let mut mins = 0; + let mut secs = 0; + let precision = match self.precision { + OffsetPrecision::Hours => { + // Minutes and seconds are simply truncated + hours = (off / 3600) as u8; + OffsetPrecision::Hours + } + OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { + // Round seconds to the nearest minute, but never roll over to 24:00. A + // `FixedOffset` may be as large as 23:59:59, and rounding that up would emit + // an invalid "+24:00" that no offset parser (including ours) accepts. + let minutes = ((off + 30) / 60).min(24 * 60 - 1); + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } + OffsetPrecision::Seconds + | OffsetPrecision::OptionalSeconds + | OffsetPrecision::OptionalMinutesAndSeconds => { + let minutes = off / 60; + secs = (off % 60) as u8; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision != OffsetPrecision::Seconds && secs == 0 { + if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } else { + OffsetPrecision::Seconds + } + } + }; + let colons = self.colons == Colons::Colon; + + if hours < 10 { + if self.padding == Pad::Space { + w.write_char(' ')?; + } + w.write_char(sign)?; + if self.padding == Pad::Zero { + w.write_char('0')?; + } + w.write_char((b'0' + hours) as char)?; + } else { + w.write_char(sign)?; + write_hundreds(w, hours)?; + } + if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision { + if colons { + w.write_char(':')?; + } + write_hundreds(w, mins)?; + } + if let OffsetPrecision::Seconds = precision { + if colons { + w.write_char(':')?; + } + write_hundreds(w, secs)?; + } + Ok(()) + } +} + +/// Specific formatting options for seconds. This may be extended in the +/// future, so exhaustive matching in external code is not recommended. +/// +/// See the `TimeZone::to_rfc3339_opts` function for usage. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::manual_non_exhaustive)] +pub enum SecondsFormat { + /// Format whole seconds only, with no decimal point nor subseconds. + Secs, + + /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3]. + Millis, + + /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6]. + Micros, + + /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9]. + Nanos, + + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available + /// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond]. + AutoSi, + + // Do not match against this. + #[doc(hidden)] + __NonExhaustive, +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[inline] +#[cfg(any(feature = "alloc", feature = "serde"))] +pub(crate) fn write_rfc3339( + w: &mut (impl Write + ?Sized), + dt: NaiveDateTime, + off: FixedOffset, + secform: SecondsFormat, + use_z: bool, +) -> fmt::Result { + let year = dt.date().year(); + if (0..=9999).contains(&year) { + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(w, "{year:+05}")?; + } + w.write_char('-')?; + write_hundreds(w, dt.date().month() as u8)?; + w.write_char('-')?; + write_hundreds(w, dt.date().day() as u8)?; + + w.write_char('T')?; + + let (hour, min, mut sec) = dt.time().hms(); + let mut nano = dt.nanosecond(); + if nano >= 1_000_000_000 { + sec += 1; + nano -= 1_000_000_000; + } + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec; + write_hundreds(w, sec as u8)?; + + match secform { + SecondsFormat::Secs => {} + SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?, + SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?, + SecondsFormat::Nanos => write!(w, ".{nano:09}")?, + SecondsFormat::AutoSi => { + if nano == 0 { + } else if nano % 1_000_000 == 0 { + write!(w, ".{:03}", nano / 1_000_000)? + } else if nano % 1_000 == 0 { + write!(w, ".{:06}", nano / 1_000)? + } else { + write!(w, ".{nano:09}")? + } + } + SecondsFormat::__NonExhaustive => unreachable!(), + }; + + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: use_z, + padding: Pad::Zero, + } + .format(w, off) +} + +#[cfg(feature = "alloc")] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + w: &mut (impl Write + ?Sized), + dt: NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + let year = dt.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + let english = default_locale(); + + w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?; + w.write_str(", ")?; + let day = dt.day(); + if day < 10 { + w.write_char((b'0' + day as u8) as char)?; + } else { + write_hundreds(w, day as u8)?; + } + w.write_char(' ')?; + w.write_str(short_months(english)[dt.month0() as usize])?; + w.write_char(' ')?; + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + w.write_char(' ')?; + + let (hour, min, sec) = dt.time().hms(); + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec + dt.nanosecond() / 1_000_000_000; + write_hundreds(w, sec as u8)?; + w.write_char(' ')?; + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) +} + +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut (impl Write + ?Sized), n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} + +#[cfg(test)] +#[cfg(feature = "alloc")] +mod tests { + use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; + use crate::FixedOffset; + #[cfg(feature = "alloc")] + use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; + + #[cfg(feature = "alloc")] + #[test] + fn test_delayed_write_to() { + let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + assert_eq!(dt_str, "2022-02-01 13:50:00.123456789"); + } + + #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] + #[test] + fn test_with_locale_delayed_write_to() { + use crate::DateTime; + use crate::format::locales::Locale; + + let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + + assert_eq!(dt_str, "火曜日, 2月 01, 2022"); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_date_format() { + let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); + assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); + assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); + assert_eq!(d.format("%q").to_string(), "1"); + assert_eq!(d.format("%d,%e").to_string(), "04, 4"); + assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); + assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); + assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year + assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); + assert_eq!(d.format("%F").to_string(), "2012-03-04"); + assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); + assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // non-four-digit years + assert_eq!( + NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), + "+12345" + ); + assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); + assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); + assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); + assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); + assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); + assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); + assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); + assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); + assert_eq!( + NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), + "-12345" + ); + + // corner cases + assert_eq!( + NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2008,08,52,53,01" + ); + assert_eq!( + NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2009,09,01,00,53" + ); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_time_format() { + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); + assert_eq!(t.format("%M").to_string(), "05"); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); + assert_eq!(t.format("%R").to_string(), "03:05"); + assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); + assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); + assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); + + let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); + + let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); + + // corner cases + assert_eq!( + NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), + "01:57:09 PM" + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), + "23:59:60" + ); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_datetime_format() { + let dt = + NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); + assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); + assert_eq!(dt.format("%s").to_string(), "1283929614"); + assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // a horror of leap second: coming near to you. + let dt = NaiveDate::from_ymd_opt(2012, 6, 30) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap(); + assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); + assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. + } + + #[test] + #[cfg(feature = "alloc")] + fn test_datetime_format_alignment() { + let datetime = Utc + .with_ymd_and_hms(2007, 1, 2, 12, 34, 56) + .unwrap() + .with_nanosecond(123456789) + .unwrap(); + + // Item::Literal, odd number of padding bytes. + let percent = datetime.format("%%"); + assert_eq!(" %", format!("{percent:>4}")); + assert_eq!("% ", format!("{percent:<4}")); + assert_eq!(" % ", format!("{percent:^4}")); + + // Item::Numeric, custom non-ASCII padding character + let year = datetime.format("%Y"); + assert_eq!("——2007", format!("{year:—>6}")); + assert_eq!("2007——", format!("{year:—<6}")); + assert_eq!("—2007—", format!("{year:—^6}")); + + // Item::Fixed + let tz = datetime.format("%Z"); + assert_eq!(" UTC", format!("{tz:>5}")); + assert_eq!("UTC ", format!("{tz:<5}")); + assert_eq!(" UTC ", format!("{tz:^5}")); + + // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] + let ymd = datetime.format("%Y %B %d"); + assert_eq!(" 2007 January 02", format!("{ymd:>17}")); + assert_eq!("2007 January 02 ", format!("{ymd:<17}")); + assert_eq!(" 2007 January 02 ", format!("{ymd:^17}")); + + // Truncated + let time = datetime.format("%T%.6f"); + assert_eq!("12:34:56.1234", format!("{time:.13}")); + } + + #[test] + fn test_offset_formatting() { + fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) { + fn check( + precision: OffsetPrecision, + colons: Colons, + padding: Pad, + allow_zulu: bool, + offsets: [FixedOffset; 7], + expected: [&str; 7], + ) { + let offset_format = OffsetFormat { precision, colons, allow_zulu, padding }; + for (offset, expected) in offsets.iter().zip(expected.iter()) { + let mut output = String::new(); + offset_format.format(&mut output, *offset).unwrap(); + assert_eq!(&output, expected); + } + } + // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00 + let offsets = [ + FixedOffset::east_opt(13_500).unwrap(), + FixedOffset::east_opt(-12_600).unwrap(), + FixedOffset::east_opt(39_600).unwrap(), + FixedOffset::east_opt(-39_622).unwrap(), + FixedOffset::east_opt(9266).unwrap(), + FixedOffset::east_opt(-45270).unwrap(), + FixedOffset::east_opt(0).unwrap(), + ]; + check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]); + check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]); + check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]); + check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]); + check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]); + check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]); + check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::None, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::None, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::None, Pad::None, false, offsets, expected[10]); + check(precision, Colons::None, Pad::None, true, offsets, expected[11]); + // `Colons::Maybe` should format the same as `Colons::None` + check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]); + check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]); + } + check_all( + OffsetPrecision::Hours, + [ + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ], + ); + check_all( + OffsetPrecision::Minutes, + [ + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"], + ], + ); + #[rustfmt::skip] + check_all( + OffsetPrecision::Seconds, + [ + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"], + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutes, + [ + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"], + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"], + [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"], + [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalSeconds, + [ + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutesAndSeconds, + [ + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"], + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"], + ], + ); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_offset_minute_rounding_no_24h_rollover() { + // The largest representable `FixedOffset` is 23:59:59. Rounding it (or anything + // from 23:59:30 onwards) to whole minutes must not produce "+24:00", which is not + // a valid offset and does not round-trip through `parse_from_rfc3339`. + for (secs, colons, expected) in [ + (86_399, Colons::Colon, "+23:59"), // +23:59:59 + (86_399, Colons::None, "+2359"), + (86_370, Colons::Colon, "+23:59"), // +23:59:30, the bottom of the rounding window + (-86_399, Colons::Colon, "-23:59"), + (-86_370, Colons::None, "-2359"), + // Regular rounding is unaffected: 23:59:29 still rounds down to 23:59. + (86_369, Colons::Colon, "+23:59"), + ] { + let off = FixedOffset::east_opt(secs).unwrap(); + let mut s = String::new(); + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons, + allow_zulu: false, + padding: Pad::Zero, + } + .format(&mut s, off) + .unwrap(); + assert_eq!(s, expected, "offset {secs}s formatted incorrectly"); + } + + // The bug was reachable through the public `to_rfc3339`, and the result must parse + // back rather than fail with `OutOfRange`. + let dt = + FixedOffset::east_opt(86_399).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + let rfc = dt.to_rfc3339(); + assert_eq!(rfc, "2020-01-01T00:00:00+23:59"); + assert!(crate::DateTime::parse_from_rfc3339(&rfc).is_ok()); + } +} diff --git a/src/format/locales.rs b/src/format/locales.rs index f7b4bbde5b..c615476c0c 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,33 +1,103 @@ -use pure_rust_locales::{locale_match, Locale}; +#[cfg(feature = "unstable-locales")] +mod localized { + use pure_rust_locales::{Locale, locale_match}; -pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABMON) -} + pub(crate) const fn default_locale() -> Locale { + Locale::POSIX + } -pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::MON) -} + pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABMON) + } -pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABDAY) -} + pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::MON) + } -pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::DAY) -} + pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABDAY) + } -pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::AM_PM) -} + pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::DAY) + } -pub(crate) fn d_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_FMT) -} + pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::AM_PM) + } + + pub(crate) const fn decimal_point(locale: Locale) -> &'static str { + locale_match!(locale => LC_NUMERIC::DECIMAL_POINT) + } + + pub(crate) const fn d_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_FMT) + } + + pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_T_FMT) + } + + pub(crate) const fn t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT) + } -pub(crate) fn d_t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_T_FMT) + pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT_AMPM) + } } -pub(crate) fn t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::T_FMT) +#[cfg(feature = "unstable-locales")] +pub(crate) use localized::*; +#[cfg(feature = "unstable-locales")] +pub use pure_rust_locales::Locale; + +#[cfg(not(feature = "unstable-locales"))] +mod unlocalized { + #[derive(Copy, Clone, Debug)] + pub(crate) struct Locale; + + pub(crate) const fn default_locale() -> Locale { + Locale + } + + pub(crate) const fn short_months(_locale: Locale) -> &'static [&'static str] { + &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + } + + pub(crate) const fn long_months(_locale: Locale) -> &'static [&'static str] { + &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + } + + pub(crate) const fn short_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + } + + pub(crate) const fn long_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + } + + pub(crate) const fn am_pm(_locale: Locale) -> &'static [&'static str] { + &["AM", "PM"] + } + + pub(crate) const fn decimal_point(_locale: Locale) -> &'static str { + "." + } } + +#[cfg(not(feature = "unstable-locales"))] +pub(crate) use unlocalized::*; diff --git a/src/format/mod.rs b/src/format/mod.rs index ba1721d495..ec4beae453 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -16,64 +16,69 @@ //! C's `strftime` format. The available options can be found [here](./strftime/index.html). //! //! # Example -//! ```rust -//! # use std::error::Error; -//! # -//! # fn main() -> Result<(), Box> { -//! use chrono::prelude::*; +//! ``` +//! # #[cfg(feature = "alloc")] { +//! use chrono::{NaiveDateTime, TimeZone, Utc}; //! -//! let date_time = Utc.ymd(2020, 11, 10).and_hms(0, 1, 32); +//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); //! //! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); //! assert_eq!(formatted, "2020-11-10 00:01:32"); //! -//! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?; +//! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc(); //! assert_eq!(parsed, date_time); -//! # Ok(()) //! # } +//! # Ok::<(), chrono::ParseError>(()) //! ``` -#![allow(ellipsis_inclusive_range_patterns)] - -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::boxed::Box; -#[cfg(feature = "alloc")] -use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; +#[cfg(all(feature = "core-error", not(feature = "std")))] +use core::error::Error; use core::fmt; use core::str::FromStr; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::error::Error; -#[cfg(any(feature = "alloc", feature = "std", test))] -use naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use {Datelike, Timelike}; -use {Month, ParseMonthError, ParseWeekdayError, Weekday}; +use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; -#[cfg(feature = "unstable-locales")] +mod formatting; +mod parsed; + +// due to the size of parsing routines, they are in separate modules. +mod parse; +pub(crate) mod scan; + +pub mod strftime; + +#[allow(unused)] +// TODO: remove '#[allow(unused)]' once we use this module for parsing or something else that does +// not require `alloc`. pub(crate) mod locales; -pub use self::parse::parse; -pub use self::parsed::Parsed; -pub use self::strftime::StrftimeItems; -/// L10n locales. +pub use formatting::SecondsFormat; +pub(crate) use formatting::write_hundreds; +#[cfg(feature = "alloc")] +pub(crate) use formatting::write_rfc2822; +#[cfg(any(feature = "alloc", feature = "serde"))] +pub(crate) use formatting::write_rfc3339; +#[cfg(feature = "alloc")] +#[allow(deprecated)] +pub use formatting::{DelayedFormat, format, format_item}; #[cfg(feature = "unstable-locales")] -pub use pure_rust_locales::Locale; - -#[cfg(not(feature = "unstable-locales"))] -#[derive(Debug)] -struct Locale; +pub use locales::Locale; +pub(crate) use parse::parse_rfc3339; +pub use parse::{parse, parse_and_remainder}; +pub use parsed::Parsed; +pub use strftime::StrftimeItems; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] enum Void {} /// Padding characters for numeric items. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Pad { /// No padding. None, @@ -96,10 +101,12 @@ pub enum Pad { /// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. -#[derive(Clone, PartialEq, Eq, Debug)] +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). - /// May accept years before 1 BCE or after 9999 CE, given an initial sign. + /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). Year, /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year. YearDiv100, @@ -112,6 +119,8 @@ pub enum Numeric { IsoYearDiv100, /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative. IsoYearMod100, + /// Quarter (FW=PW=1). + Quarter, /// Month (FW=PW=2). Month, /// Day of the month (FW=PW=2). @@ -152,35 +161,31 @@ pub enum Numeric { } /// An opaque type representing numeric item types for internal uses only. +#[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } -impl Clone for InternalNumeric { - fn clone(&self) -> Self { - match self._dummy {} - } -} - -impl PartialEq for InternalNumeric { - fn eq(&self, _other: &InternalNumeric) -> bool { - match self._dummy {} - } -} - -impl Eq for InternalNumeric {} - impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") } } +#[cfg(feature = "defmt")] +impl defmt::Format for InternalNumeric { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "") + } +} + /// Fixed-format item types. /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. -#[derive(Clone, PartialEq, Eq, Debug)] +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Fixed { /// Abbreviated month names. /// @@ -226,6 +231,18 @@ pub enum Fixed { /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, + /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24:00:00` to `+24:00:00`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetDoubleColon, + /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24` to `+24`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetTripleColon, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). /// /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, @@ -252,12 +269,14 @@ pub enum Fixed { } /// An opaque type representing fixed-format item types for internal uses only. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct InternalFixed { val: InternalInternal, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -276,18 +295,66 @@ enum InternalInternal { Nanosecond9NoDot, } +/// Type for specifying the format of UTC offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OffsetFormat { + /// See `OffsetPrecision`. + pub precision: OffsetPrecision, + /// Separator between hours, minutes and seconds. + pub colons: Colons, + /// Represent `+00:00` as `Z`. + pub allow_zulu: bool, + /// Pad the hour value to two digits. + pub padding: Pad, +} + +/// The precision of an offset from UTC formatting item. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OffsetPrecision { + /// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to + /// have an offset of 30 minutes, 15 minutes, etc. + /// Any minutes and seconds get truncated. + Hours, + /// Format offset from UTC as hours and minutes. + /// Any seconds will be rounded to the nearest minute. + Minutes, + /// Format offset from UTC as hours, minutes and seconds. + Seconds, + /// Format offset from UTC as hours, and optionally with minutes. + /// Any seconds will be rounded to the nearest minute. + OptionalMinutes, + /// Format offset from UTC as hours and minutes, and optionally seconds. + OptionalSeconds, + /// Format offset from UTC as hours and optionally minutes and seconds. + OptionalMinutesAndSeconds, +} + +/// The separator between hours and minutes in an offset. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Colons { + /// No separator + None, + /// Colon (`:`) as separator + Colon, + /// No separator when formatting, colon allowed when parsing. + Maybe, +} + /// A single formatting item. This is used for both formatting and parsing. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same as `Literal` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -298,49 +365,76 @@ pub enum Item<'a> { Error, } -macro_rules! lit { - ($x:expr) => { - Item::Literal($x) - }; +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Item<'a> { + fn format(&self, f: defmt::Formatter) { + match self { + Item::Literal(v) => defmt::write!(f, "Literal {{ {} }}", v), + #[cfg(feature = "alloc")] + Item::OwnedLiteral(_) => {} + Item::Space(v) => defmt::write!(f, "Space {{ {} }}", v), + #[cfg(feature = "alloc")] + Item::OwnedSpace(_) => {} + Item::Numeric(u, v) => defmt::write!(f, "Numeric {{ {}, {} }}", u, v), + Item::Fixed(v) => defmt::write!(f, "Fixed {{ {} }}", v), + Item::Error => defmt::write!(f, "Error"), + } + } } -macro_rules! sp { - ($x:expr) => { - Item::Space($x) - }; + +const fn num(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::None) } -macro_rules! num { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::None) - }; + +const fn num0(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Zero) } -macro_rules! num0 { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Zero) - }; + +const fn nums(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Space) } -macro_rules! nums { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Space) - }; + +const fn fixed(fixed: Fixed) -> Item<'static> { + Item::Fixed(fixed) } -macro_rules! fix { - ($x:ident) => { - Item::Fixed(Fixed::$x) - }; + +const fn internal_fixed(val: InternalInternal) -> Item<'static> { + Item::Fixed(Fixed::Internal(InternalFixed { val })) } -macro_rules! internal_fix { - ($x:ident) => { - Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x })) - }; + +impl Item<'_> { + /// Convert items that contain a reference to the format string into an owned variant. + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn to_owned(self) -> Item<'static> { + match self { + Item::Literal(s) => Item::OwnedLiteral(Box::from(s)), + Item::Space(s) => Item::OwnedSpace(Box::from(s)), + Item::Numeric(n, p) => Item::Numeric(n, p), + Item::Fixed(f) => Item::Fixed(f), + Item::OwnedLiteral(l) => Item::OwnedLiteral(l), + Item::OwnedSpace(s) => Item::OwnedSpace(s), + Item::Error => Item::Error, + } + } } /// An error from the `parse` function. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ParseError(ParseErrorKind); +impl ParseError { + /// The category of parse error + pub const fn kind(&self) -> ParseErrorKind { + self.0 + } +} + /// The category of parse error -#[derive(Debug, Clone, PartialEq, Eq, Copy)] -enum ParseErrorKind { +#[allow(clippy::manual_non_exhaustive)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, @@ -366,8 +460,12 @@ enum ParseErrorKind { /// All formatting items have been read but there is a remaining input. TooLong, - /// There was an error on the formatting string, or there were non-supported formating items. + /// There was an error on the formatting string, or there were non-supported formatting items. BadFormat, + + // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. + #[doc(hidden)] + __Nonexhaustive, } /// Same as `Result`. @@ -383,11 +481,12 @@ impl fmt::Display for ParseError { ParseErrorKind::TooShort => write!(f, "premature end of input"), ParseErrorKind::TooLong => write!(f, "trailing input"), ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"), + _ => unreachable!(), } } } -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "core-error", feature = "std"))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { @@ -396,445 +495,17 @@ impl Error for ParseError { } // to be used in this module and submodules -const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); +pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); -const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); +pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); -/// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] -pub fn format_item<'a>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, None)?; - w.pad(&result) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner<'a>( - result: &mut String, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, - _locale: Option, -) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - let locale = _locale.unwrap_or(Locale::POSIX); - let am_pm = locales::am_pm(locale); - ( - locales::short_months(locale), - locales::long_months(locale), - locales::short_weekdays(locale), - locales::long_weekdays(locale), - am_pm, - &[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()], - ) - }; - #[cfg(not(feature = "unstable-locales"))] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - ( - &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - &["AM", "PM"], - &["am", "pm"], - ) - }; - - use core::fmt::Write; - use div::{div_floor, mod_floor}; - - match *item { - Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7 - }; - let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7 - }; - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))), - YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))), - IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some((d.and_time(*t) - off).timestamp()) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(result, "{:+}", v), - Pad::Zero => write!(result, "{:+01$}", v, width + 1), - Pad::Space => write!(result, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(result, "{}", v), - Pad::Zero => write!(result, "{:01$}", v, width), - Pad::Space => write!(result, "{:1$}", v, width), - } - }? - } else { - return Err(fmt::Error); // insufficient arguments for given format - } - } - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. - /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. - fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - use_colon: bool, - ) -> fmt::Result { - let off = off.local_minus_utc(); - if !allow_zulu || off != 0 { - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - if use_colon { - write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) - } else { - write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) - } - } else { - result.push('Z'); - Ok(()) - } - } - - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result - .push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]); - Ok(()) - }), - LowerAmPm => time.map(|t| { - #[cfg_attr(feature = "cargo-clippy", allow(useless_asref))] - { - result.push_str(if t.hour12().0 { - am_pm_lowercase[1].as_ref() - } else { - am_pm_lowercase[0].as_ref() - }); - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] }); - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|&(ref name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffsetColon => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, true)) - } - TimezoneOffsetColonZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, true)) - } - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, false)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, false)) - } - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - panic!("Do not try to write %#z it is undefined") - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - let sec = t.second() + t.nanosecond() / 1_000_000_000; - write!( - result, - "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", - short_weekdays[d.weekday().num_days_from_sunday() as usize], - d.day(), - short_months[d.month0() as usize], - d.year(), - t.hour(), - t.minute(), - sec - )?; - Some(write_local_minus_utc(result, off, false, false)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(result, "{:?}T{:?}", d, t)?; - Some(write_local_minus_utc(result, off, false, true)) - } else { - None - } - } - }; - - match ret { - Some(ret) => ret?, - None => return Err(fmt::Error), // insufficient arguments for given format - } - } - - Item::Error => return Err(fmt::Error), - } - Ok(()) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] -pub fn format<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), None)?; - } - w.pad(&result) -} - -mod parsed; - -// due to the size of parsing routines, they are in separate modules. -mod parse; -mod scan; - -pub mod strftime; - -/// A *temporary* object which can be used as an argument to `format!` or others. -/// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug)] -pub struct DelayedFormat { - /// The date view, if any. - date: Option, - /// The time view, if any. - time: Option, - /// The name and local-to-UTC difference for the offset (timezone), if any. - off: Option<(String, FixedOffset)>, - /// An iterator returning formatting items. - items: I, - /// Locale used for text. - // TODO: Only used with the locale feature. We should make this property - // only present when the feature is enabled. - #[allow(dead_code)] - locale: Option, -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { - /// Makes a new `DelayedFormat` value out of local date and time. - pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: None } - } - - /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. - pub fn new_with_offset( - date: Option, - time: Option, - offset: &Off, - items: I, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and locale. - #[cfg(feature = "unstable-locales")] - pub fn new_with_locale( - date: Option, - time: Option, - items: I, - locale: Locale, - ) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) } - } - - /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. - #[cfg(feature = "unstable-locales")] - pub fn new_with_offset_and_locale( - date: Option, - time: Option, - offset: &Off, - items: I, - locale: Locale, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: Some(locale), - } - } -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - { - if let Some(locale) = self.locale { - return format_localized( - f, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - self.items.clone(), - locale, - ); - } - } - - format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) - } -} - // this implementation is here only because we need some private code from `scan` -/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html). /// /// # Example /// @@ -870,44 +541,7 @@ impl FromStr for Weekday { } } -/// Formats single formatting item -#[cfg(feature = "unstable-locales")] -pub fn format_item_localized<'a>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, - locale: Locale, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, Some(locale))?; - w.pad(&result) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(feature = "unstable-locales")] -pub fn format_localized<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, - locale: Locale, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; - } - w.pad(&result) -} - -/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html). /// /// # Example /// diff --git a/src/format/parse.rs b/src/format/parse.rs index 175e4c66d6..18857a286a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -4,17 +4,14 @@ //! Date and time parsing routines. -#![allow(deprecated)] - use core::borrow::Borrow; use core::str; -use core::usize; use super::scan; +use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; -use super::{ParseError, ParseErrorKind, ParseResult}; -use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; -use {DateTime, FixedOffset, Weekday}; +use super::{ParseError, ParseResult}; +use crate::{DateTime, FixedOffset, MappedLocalTime, NaiveDate, NaiveTime, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -53,7 +50,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // an adapted RFC 2822 syntax from Section 3.3 and 4.3: // - // date-time = [ day-of-week "," ] date 1*S time *S + // c-char = + // c-escape = "\" + // comment = "(" *(comment / c-char / c-escape) ")" *S + // date-time = [ day-of-week "," ] date 1*S time *S *comment // day-of-week = *S day-name *S // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year @@ -79,9 +79,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // // - we do not recognize a folding white space (FWS) or comment (CFWS). // for our purposes, instead, we accept any sequence of Unicode - // white space characters (denoted here to `S`). any actual RFC 2822 - // parser is expected to parse FWS and/or CFWS themselves and replace - // it with a single SP (`%x20`); this is legitimate. + // white space characters (denoted here to `S`). For comments, we accept + // any text within parentheses while respecting escaped parentheses. + // Any actual RFC 2822 parser is expected to parse FWS and/or CFWS themselves + // and replace it with a single SP (`%x20`); this is legitimate. // // - two-digit year < 50 should be interpreted by adding 2000. // two-digit year >= 50 or three-digit year should be interpreted @@ -96,7 +97,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // since we do not directly go to a `DateTime` so one can recover // the offset information from `Parsed` anyway. - s = s.trim_left(); + s = s.trim_start(); if let Ok((s_, weekday)) = scan::short_weekday(s) { if !s_.starts_with(',') { @@ -106,7 +107,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_weekday(weekday)?; } - s = s.trim_left(); + s = s.trim_start(); parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; s = scan::space(s)?; // mandatory parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; @@ -117,10 +118,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st let mut year = try_consume!(scan::number(s, 2, usize::MAX)); let yearlen = prevlen - s.len(); match (yearlen, year) { - (2, 0...49) => { + (2, 0..=49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 - (2, 50...99) => { + (2, 50..=99) => { year += 1900; } // 79 -> 1979 (3, _) => { @@ -132,23 +133,25 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st s = scan::space(s)?; // mandatory parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S + s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - if let Ok(s_) = scan::char(s.trim_left(), b':') { + if let Ok(s_) = scan::char(s.trim_start(), b':') { // [ ":" *S 2DIGIT ] parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; } s = scan::space(s)?; // mandatory - if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { - // only set the offset when it is definitely known (i.e. not `-0000`) - parsed.set_offset(i64::from(offset))?; + parsed.set_offset(i64::from(try_consume!(scan::timezone_offset_2822(s))))?; + + // optional comments + while let Ok((s_out, ())) = scan::comment_2822(s) { + s = s_out; } Ok((s, ())) } -fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { +pub(crate) fn parse_rfc3339(mut s: &str) -> ParseResult> { macro_rules! try_consume { ($e:expr) => {{ let (s_, v) = $e?; @@ -183,36 +186,84 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. // note that this restriction is unique to RFC 3339 and not ISO 8601. // since this is not a typical Chrono behavior, we check it earlier. + // + // - For readability a full-date and a full-time may be separated by a space character. - parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; - s = scan::char(s, b'-')?; - parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b'-')?; - parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; + let bytes = s.as_bytes(); + if bytes.len() < 19 { + return Err(TOO_SHORT); + } - s = match s.as_bytes().first() { - Some(&b't') | Some(&b'T') => &s[1..], - Some(_) => return Err(INVALID), - None => return Err(TOO_SHORT), - }; + let fixed = <&[u8; 19]>::try_from(&bytes[..19]).unwrap(); // we just checked the length + let year = digit(fixed, 0)? as u16 * 1000 + + digit(fixed, 1)? as u16 * 100 + + digit(fixed, 2)? as u16 * 10 + + digit(fixed, 3)? as u16; + if bytes.get(4) != Some(&b'-') { + return Err(INVALID); + } - parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b':')?; - parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b':')?; - parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; - if s.starts_with('.') { - let nanosecond = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nanosecond)?; + let month = digit(fixed, 5)? * 10 + digit(fixed, 6)?; + if bytes.get(7) != Some(&b'-') { + return Err(INVALID); + } + + let day = digit(fixed, 8)? * 10 + digit(fixed, 9)?; + let date = + NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).ok_or(OUT_OF_RANGE)?; + + if !matches!(bytes.get(10), Some(&b't' | &b'T' | &b' ')) { + return Err(INVALID); } - let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':'))); - if offset <= -86_400 || offset >= 86_400 { - return Err(OUT_OF_RANGE); + let hour = digit(fixed, 11)? * 10 + digit(fixed, 12)?; + if bytes.get(13) != Some(&b':') { + return Err(INVALID); } - parsed.set_offset(i64::from(offset))?; - Ok((s, ())) + let min = digit(fixed, 14)? * 10 + digit(fixed, 15)?; + if bytes.get(16) != Some(&b':') { + return Err(INVALID); + } + + let sec = digit(fixed, 17)? * 10 + digit(fixed, 18)?; + let (sec, extra_nanos) = match sec { + 60 => (59, 1_000_000_000), // rfc3339 allows leap seconds + _ => (sec, 0), + }; + + let nano = if bytes.get(19) == Some(&b'.') { + let nanosecond = try_consume!(scan::nanosecond(&s[20..])); + extra_nanos + nanosecond + } else { + s = &s[19..]; + extra_nanos + }; + + let time = NaiveTime::from_hms_nano_opt(hour as u32, min as u32, sec as u32, nano) + .ok_or(OUT_OF_RANGE)?; + + // Max for the hours field is `23`, and for the minutes field `59`. + let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); + if !s.is_empty() { + return Err(TOO_LONG); + } + + let tz = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + Ok(match date.and_time(time).and_local_timezone(tz) { + MappedLocalTime::Single(dt) => dt, + // `FixedOffset::with_ymd_and_hms` doesn't return `MappedLocalTime::Ambiguous` + // and returns `MappedLocalTime::None` on invalid data + MappedLocalTime::Ambiguous(_, _) | MappedLocalTime::None => unreachable!(), + }) +} + +#[inline] +fn digit(bytes: &[u8; 19], index: usize) -> ParseResult { + match bytes[index].is_ascii_digit() { + true => Ok(bytes[index] - b'0'), + false => Err(INVALID), + } } /// Tries to parse given string into `parsed` with given formatting items. @@ -236,14 +287,44 @@ where I: Iterator, B: Borrow>, { - parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) + match parse_internal(parsed, s, items) { + Ok("") => Ok(()), + Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error + Err(e) => Err(e), + } +} + +/// Tries to parse given string into `parsed` with given formatting items. +/// Returns `Ok` with a slice of the unparsed remainder. +/// +/// This particular date and time parser is: +/// +/// - Greedy. It will consume the longest possible prefix. +/// For example, `April` is always consumed entirely when the long month name is requested; +/// it equally accepts `Apr`, but prefers the longer prefix in this case. +/// +/// - Padding-agnostic (for numeric items). +/// The [`Pad`](./enum.Pad.html) field is completely ignored, +/// so one can prepend any number of zeroes before numbers. +/// +/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. +pub fn parse_and_remainder<'a, 'b, I, B>( + parsed: &mut Parsed, + s: &'b str, + items: I, +) -> ParseResult<&'b str> +where + I: Iterator, + B: Borrow>, +{ + parse_internal(parsed, s, items) } fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, items: I, -) -> Result<&'b str, (&'b str, ParseError)> +) -> Result<&'b str, ParseError> where I: Iterator, B: Borrow>, @@ -255,7 +336,7 @@ where s = s_; v } - Err(e) => return Err((s, e)), + Err(e) => return Err(e), } }}; } @@ -264,32 +345,32 @@ where match *item.borrow() { Item::Literal(prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(prefix) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(&prefix[..]) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } Item::Space(_) => { - s = s.trim_left(); + s = s.trim_start(); } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] Item::OwnedSpace(_) => { - s = s.trim_left(); + s = s.trim_start(); } Item::Numeric(ref spec, ref _pad) => { @@ -303,6 +384,7 @@ where IsoYear => (4, true, Parsed::set_isoyear), IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), + Quarter => (1, false, Parsed::set_quarter), Month => (2, false, Parsed::set_month), Day => (2, false, Parsed::set_day), WeekFromSun => (2, false, Parsed::set_week_from_sun), @@ -322,11 +404,11 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); + s = s.trim_start(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); - 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))? + 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? } else if s.starts_with('+') { try_consume!(scan::number(&s[1..], 1, usize::MAX)) } else { @@ -336,7 +418,7 @@ where } else { try_consume!(scan::number(s, 1, width)) }; - set(parsed, v).map_err(|e| (s, e))?; + set(parsed, v)?; } Item::Fixed(ref spec) => { @@ -345,601 +427,1515 @@ where match spec { &ShortMonthName => { let month0 = try_consume!(scan::short_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &LongMonthName => { let month0 = try_consume!(scan::short_or_long_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &ShortWeekdayName => { let weekday = try_consume!(scan::short_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LongWeekdayName => { let weekday = try_consume!(scan::short_or_long_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LowerAmPm | &UpperAmPm => { if s.len() < 2 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { (b'a', b'm') => false, (b'p', b'm') => true, - _ => return Err((s, INVALID)), + _ => return Err(INVALID), }; - parsed.set_ampm(ampm).map_err(|e| (s, e))?; + parsed.set_ampm(ampm)?; s = &s[2..]; } - &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { + &Nanosecond => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano as i64)?; + } + } + + &Nanosecond3 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 3)); + parsed.set_nanosecond(nano)?; + } + } + + &Nanosecond6 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 6)); + parsed.set_nanosecond(nano)?; + } + } + + &Nanosecond9 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 9)); + parsed.set_nanosecond(nano)?; } } &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 3)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { if s.len() < 6 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { if s.len() < 9 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &TimezoneName => { - try_consume!(scan::timezone_name_skip(s)); + try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); } - &TimezoneOffsetColon | &TimezoneOffset => { + &TimezoneOffsetColon + | &TimezoneOffsetDoubleColon + | &TimezoneOffsetTripleColon + | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( - s.trim_left(), - scan::colon_or_space + s.trim_start(), + scan::colon_or_space, + false, + false, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), - scan::colon_or_space + let offset = try_consume!(scan::timezone_offset( + s.trim_start(), + scan::colon_or_space, + true, + false, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), - scan::colon_or_space + let offset = try_consume!(scan::timezone_offset( + s.trim_start(), + scan::colon_or_space, + true, + true, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), - &RFC3339 => try_consume!(parse_rfc3339(parsed, s)), + &RFC3339 => { + // Used for the `%+` specifier, which has the description: + // "Same as `%Y-%m-%dT%H:%M:%S%.f%:z` (...) + // This format also supports having a `Z` or `UTC` in place of `%:z`." + // Use the relaxed parser to match this description. + try_consume!(parse_rfc3339_relaxed(parsed, s)) + } } } Item::Error => { - return Err((s, BAD_FORMAT)); + return Err(BAD_FORMAT); } } } - - // if there are trailling chars, it is an error - if !s.is_empty() { - Err((s, TOO_LONG)) - } else { - Ok(s) - } + Ok(s) } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time +/// A space or a 'T' are accepted as the separator between the date and time /// parts. Additional spaces are allowed between each component. /// /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012- 12-12T12: 12:12Z".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { - const DATE_ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - ]; - const TIME_ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), - ]; - let mut parsed = Parsed::new(); - match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) { - Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { - if remainder.starts_with('T') || remainder.starts_with(' ') { - parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; - } else { - Err(INVALID)?; - } - } - Err((_s, e)) => Err(e)?, - Ok(_) => Err(NOT_ENOUGH)?, - }; + let (s, _) = parse_rfc3339_relaxed(&mut parsed, s)?; + if !s.trim_start().is_empty() { + return Err(TOO_LONG); + } parsed.to_datetime() } } -#[cfg(test)] -#[test] -fn test_parse() { - use super::IMPOSSIBLE; - use super::*; +/// Accepts a relaxed form of RFC3339. +/// +/// Differences with RFC3339: +/// - Values don't require padding to two digits. +/// - Years outside the range 0...=9999 are accepted, but they must include a sign. +/// - `UTC` is accepted as a valid timezone name/offset (for compatibility with the debug format of +/// `DateTime`. +/// - There can be spaces between any of the components. +/// - The colon in the offset may be missing. +fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { + const DATE_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + ]; + const TIME_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; - // workaround for Rust issue #22255 - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } + s = parse_internal(parsed, s, DATE_ITEMS.iter())?; - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + s = match s.as_bytes().first() { + Some(&b't' | &b'T' | &b' ') => &s[1..], + Some(_) => return Err(INVALID), + None => return Err(TOO_SHORT), + }; + + s = parse_internal(parsed, s, TIME_ITEMS.iter())?; + s = s.trim_start(); + let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { + (&s[3..], 0) + } else { + scan::timezone_offset(s, scan::colon_or_space, true, false, true)? + }; + parsed.set_offset(i64::from(offset))?; + Ok((s, ())) +} + +#[cfg(test)] +mod tests { + use crate::format::*; + use crate::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Timelike, Utc}; + + macro_rules! parsed { + ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { let mut expected = Parsed::new(); $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) + Ok(expected) }); } - // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); - - // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); - - // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); - - // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\x005", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - - // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); - - // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); - - // various numeric fields - check!("1234 5678", - [num!(Year), num!(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); - - // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack - - // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); - - // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming - - // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); - - // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - - // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; - year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); -} + #[test] + fn test_parse_whitespace_and_literal() { + use crate::format::Item::{Literal, Space}; + + // empty string + parses("", &[]); + check(" ", &[], Err(TOO_LONG)); + check("a", &[], Err(TOO_LONG)); + check("abc", &[], Err(TOO_LONG)); + check("🤠", &[], Err(TOO_LONG)); + + // whitespaces + parses("", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses("", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" "), Space(" ")]); + parses("\t", &[Space("")]); + parses(" \n\r \n", &[Space("")]); + parses("\t", &[Space("\t")]); + parses("\t", &[Space(" ")]); + parses(" ", &[Space("\t")]); + parses("\t\r", &[Space("\t\r")]); + parses("\t\r ", &[Space("\t\r ")]); + parses("\t \r", &[Space("\t \r")]); + parses(" \t\r", &[Space(" \t\r")]); + parses(" \n\r \n", &[Space(" \n\r \n")]); + parses(" \t\n", &[Space(" \t")]); + parses(" \n\t", &[Space(" \t\n")]); + parses("\u{2002}", &[Space("\u{2002}")]); + // most unicode whitespace characters + parses( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + &[Space( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + )], + ); + // most unicode whitespace characters + parses( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + &[ + Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}"), + ], + ); + check("a", &[Space("")], Err(TOO_LONG)); + check("a", &[Space(" ")], Err(TOO_LONG)); + // a Space containing a literal does not match a literal + check("a", &[Space("a")], Err(TOO_LONG)); + check("abc", &[Space("")], Err(TOO_LONG)); + check("abc", &[Space(" ")], Err(TOO_LONG)); + check(" abc", &[Space("")], Err(TOO_LONG)); + check(" abc", &[Space(" ")], Err(TOO_LONG)); + + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" + + // literal + parses("", &[Literal("")]); + check("", &[Literal("a")], Err(TOO_SHORT)); + check(" ", &[Literal("a")], Err(INVALID)); + parses("a", &[Literal("a")]); + parses("+", &[Literal("+")]); + parses("-", &[Literal("-")]); + parses("−", &[Literal("−")]); // MINUS SIGN (U+2212) + parses(" ", &[Literal(" ")]); // a Literal may contain whitespace and match whitespace + check("aa", &[Literal("a")], Err(TOO_LONG)); + check("🤠", &[Literal("a")], Err(INVALID)); + check("A", &[Literal("a")], Err(INVALID)); + check("a", &[Literal("z")], Err(INVALID)); + check("a", &[Literal("🤠")], Err(TOO_SHORT)); + check("a", &[Literal("\u{0363}a")], Err(TOO_SHORT)); + check("\u{0363}a", &[Literal("a")], Err(INVALID)); + parses("\u{0363}a", &[Literal("\u{0363}a")]); + check("a", &[Literal("ab")], Err(TOO_SHORT)); + parses("xy", &[Literal("xy")]); + parses("xy", &[Literal("x"), Literal("y")]); + parses("1", &[Literal("1")]); + parses("1234", &[Literal("1234")]); + parses("+1234", &[Literal("+1234")]); + parses("-1234", &[Literal("-1234")]); + parses("−1234", &[Literal("−1234")]); // MINUS SIGN (U+2212) + parses("PST", &[Literal("PST")]); + parses("🤠", &[Literal("🤠")]); + parses("🤠a", &[Literal("🤠"), Literal("a")]); + parses("🤠a🤠", &[Literal("🤠"), Literal("a🤠")]); + parses("a🤠b", &[Literal("a"), Literal("🤠"), Literal("b")]); + // literals can be together + parses("xy", &[Literal("xy")]); + parses("xyz", &[Literal("xyz")]); + // or literals can be apart + parses("xy", &[Literal("x"), Literal("y")]); + parses("xyz", &[Literal("x"), Literal("yz")]); + parses("xyz", &[Literal("xy"), Literal("z")]); + parses("xyz", &[Literal("x"), Literal("y"), Literal("z")]); + // + check("x y", &[Literal("x"), Literal("y")], Err(INVALID)); + parses("xy", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(" "), Literal("y")]); + + // whitespaces + literals + parses("a\n", &[Literal("a"), Space("\n")]); + parses("\tab\n", &[Space("\t"), Literal("ab"), Space("\n")]); + parses( + "ab\tcd\ne", + &[Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")], + ); + parses( + "+1ab\tcd\r\n+,.", + &[Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")], + ); + // whitespace and literals can be intermixed + parses("a\tb", &[Literal("a\tb")]); + parses("a\tb", &[Literal("a"), Space("\t"), Literal("b")]); + } -#[cfg(test)] -#[test] -fn test_rfc2822() { - use super::NOT_ENOUGH; - use super::*; - use offset::FixedOffset; - use DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month - ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields - ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name - ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour - ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour - ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute - ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset - ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone - ]; + #[test] + fn test_parse_numeric() { + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; + + // numeric + check("1987", &[num(Year)], parsed!(year: 1987)); + check("1987 ", &[num(Year)], Err(TOO_LONG)); + check("0x12", &[num(Year)], Err(TOO_LONG)); // `0` is parsed + check("x123", &[num(Year)], Err(INVALID)); + check("o123", &[num(Year)], Err(INVALID)); + check("2015", &[num(Year)], parsed!(year: 2015)); + check("0000", &[num(Year)], parsed!(year: 0)); + check("9999", &[num(Year)], parsed!(year: 9999)); + check(" \t987", &[num(Year)], parsed!(year: 987)); + check(" \t987", &[Space(" \t"), num(Year)], parsed!(year: 987)); + check(" \t987🤠", &[Space(" \t"), num(Year), Literal("🤠")], parsed!(year: 987)); + check("987🤠", &[num(Year), Literal("🤠")], parsed!(year: 987)); + check("5", &[num(Year)], parsed!(year: 5)); + check("5\0", &[num(Year)], Err(TOO_LONG)); + check("\x005", &[num(Year)], Err(INVALID)); + check("", &[num(Year)], Err(TOO_SHORT)); + check("12345", &[num(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[nums(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[num0(Year), Literal("5")], parsed!(year: 1234)); + check("12341234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), Space(" "), num(Year)], parsed!(year: 1234)); + check("1234 1235", &[num(Year), num(Year)], Err(IMPOSSIBLE)); + check("1234 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234x1234", &[num(Year), Literal("x"), num(Year)], parsed!(year: 1234)); + check("1234 x 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("xx"), num(Year)], parsed!(year: 1234)); + check( + "1234 x 1234", + &[num(Year), Space(" "), Literal("x"), Space(" "), num(Year)], + parsed!(year: 1234), + ); + check( + "1234 x 1235", + &[num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")], + parsed!(year: 1234), + ); - fn rfc2822_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; - parsed.to_datetime() + // signed numeric + check("-42", &[num(Year)], parsed!(year: -42)); + check("+42", &[num(Year)], parsed!(year: 42)); + check("-0042", &[num(Year)], parsed!(year: -42)); + check("+0042", &[num(Year)], parsed!(year: 42)); + check("-42195", &[num(Year)], parsed!(year: -42195)); + check("−42195", &[num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check("+42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check("-42195 ", &[num(Year)], Err(TOO_LONG)); + check("+42195 ", &[num(Year)], Err(TOO_LONG)); + check(" - 42", &[num(Year)], Err(INVALID)); + check(" + 42", &[num(Year)], Err(INVALID)); + check(" -42195", &[Space(" "), num(Year)], parsed!(year: -42195)); + check(" −42195", &[Space(" "), num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check(" +42195", &[Space(" "), num(Year)], parsed!(year: 42195)); + check(" - 42", &[Space(" "), num(Year)], Err(INVALID)); + check(" + 42", &[Space(" "), num(Year)], Err(INVALID)); + check("-", &[num(Year)], Err(TOO_SHORT)); + check("+", &[num(Year)], Err(TOO_SHORT)); + + // unsigned numeric + check("345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("+345", &[num(Ordinal)], Err(INVALID)); + check("-345", &[num(Ordinal)], Err(INVALID)); + check(" 345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("−345", &[num(Ordinal)], Err(INVALID)); // MINUS SIGN (U+2212) + check("345 ", &[num(Ordinal)], Err(TOO_LONG)); + check(" 345", &[Space(" "), num(Ordinal)], parsed!(ordinal: 345)); + check("345 ", &[num(Ordinal), Space(" ")], parsed!(ordinal: 345)); + check("345🤠 ", &[num(Ordinal), Literal("🤠"), Space(" ")], parsed!(ordinal: 345)); + check("345🤠", &[num(Ordinal)], Err(TOO_LONG)); + check("\u{0363}345", &[num(Ordinal)], Err(INVALID)); + check(" +345", &[num(Ordinal)], Err(INVALID)); + check(" -345", &[num(Ordinal)], Err(INVALID)); + check("\t345", &[Space("\t"), num(Ordinal)], parsed!(ordinal: 345)); + check(" +345", &[Space(" "), num(Ordinal)], Err(INVALID)); + check(" -345", &[Space(" "), num(Ordinal)], Err(INVALID)); + + // various numeric fields + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check( + "12 34 56 78", + &[num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)], + parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), + ); + check( + "1 1 2 3 4 5", + &[ + num(Quarter), + num(Month), + num(Day), + num(WeekFromSun), + num(NumDaysFromSun), + num(IsoWeek), + ], + parsed!(quarter: 1, month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), + ); + check( + "6 7 89 01", + &[num(WeekFromMon), num(WeekdayFromMon), num(Ordinal), num(Hour12)], + parsed!(week_from_mon: 6, weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1), + ); + check( + "23 45 6 78901234 567890123", + &[num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)], + parsed!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123), + ); } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() + #[test] + fn test_parse_fixed() { + use crate::format::Fixed::*; + use crate::format::Item::{Literal, Space}; + + // fixed: month and weekday names + check("apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("Apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("APR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("ApR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("\u{0363}APR", &[fixed(ShortMonthName)], Err(INVALID)); + check("April", &[fixed(ShortMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("A", &[fixed(ShortMonthName)], Err(TOO_SHORT)); + check("Sol", &[fixed(ShortMonthName)], Err(INVALID)); + check("Apr", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Apri", &[fixed(LongMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("April", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Aprill", &[fixed(LongMonthName)], Err(TOO_LONG)); + check("Aprill", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("Aprl", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("April", &[fixed(LongMonthName), Literal("il")], Err(TOO_SHORT)); // do not backtrack + check("thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("THU", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("tHu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(ShortWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("T", &[fixed(ShortWeekdayName)], Err(TOO_SHORT)); + check("The", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Nop", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Thu", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thur", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thurs", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thursday", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursdays", &[fixed(LongWeekdayName)], Err(TOO_LONG)); + check("Thursdays", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thus", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(LongWeekdayName), Literal("rsday")], Err(TOO_SHORT)); // do not backtrack + + // fixed: am/pm + check("am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("am", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("Am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check(" Am", &[Space(" "), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("Am🤠", &[fixed(LowerAmPm), Literal("🤠")], parsed!(hour_div_12: 0)); + check("🤠Am", &[Literal("🤠"), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("\u{0363}am", &[fixed(LowerAmPm)], Err(INVALID)); + check("\u{0360}am", &[fixed(LowerAmPm)], Err(INVALID)); + check(" Am", &[fixed(LowerAmPm)], Err(INVALID)); + check("Am ", &[fixed(LowerAmPm)], Err(TOO_LONG)); + check("a.m.", &[fixed(LowerAmPm)], Err(INVALID)); + check("A.M.", &[fixed(LowerAmPm)], Err(INVALID)); + check("ame", &[fixed(LowerAmPm)], Err(TOO_LONG)); // `am` is parsed + check("a", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("p", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("x", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("xx", &[fixed(LowerAmPm)], Err(INVALID)); + check("", &[fixed(LowerAmPm)], Err(TOO_SHORT)); } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); - } + #[test] + fn test_parse_fixed_nanosecond() { + use crate::format::Fixed::{Nanosecond, Nanosecond3, Nanosecond6, Nanosecond9}; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + use crate::format::Numeric::Second; + + // fixed: dot plus nanoseconds + check("", &[fixed(Nanosecond)], parsed!()); // no field set, but not an error + check(".", &[fixed(Nanosecond)], Err(TOO_SHORT)); + check("4", &[fixed(Nanosecond)], Err(TOO_LONG)); // never consumes `4` + check("4", &[fixed(Nanosecond), num(Second)], parsed!(second: 4)); + check(".0", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4", &[fixed(Nanosecond)], parsed!(nanosecond: 400_000_000)); + check(".42", &[fixed(Nanosecond)], parsed!(nanosecond: 420_000_000)); + check(".421", &[fixed(Nanosecond)], parsed!(nanosecond: 421_000_000)); + check(".42195", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_000)); + check(".421951", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_000)); + check(".4219512", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_200)); + check(".42195123", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_230)); + check(".421950803", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".4219508035", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".42195080354", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".421950803547", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".000000003", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000031", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000035", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".000000003547", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000009", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".000000000547", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".0000000009999999999999999999999999", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4🤠", &[fixed(Nanosecond), Literal("🤠")], parsed!(nanosecond: 400_000_000)); + check(".4x", &[fixed(Nanosecond)], Err(TOO_LONG)); + check(". 4", &[fixed(Nanosecond)], Err(INVALID)); + check(" .4", &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming + + // fixed-length fractions of a second + check("", &[fixed(Nanosecond3)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond3)], Err(TOO_LONG)); // never consumes `4` + check(".12", &[fixed(Nanosecond3)], Err(TOO_SHORT)); + check(".123", &[fixed(Nanosecond3)], parsed!(nanosecond: 123_000_000)); + check(".1234", &[fixed(Nanosecond3)], Err(TOO_LONG)); + check(".1234", &[fixed(Nanosecond3), Literal("4")], parsed!(nanosecond: 123_000_000)); + + check("", &[fixed(Nanosecond6)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond6)], Err(TOO_LONG)); // never consumes `4` + check(".12345", &[fixed(Nanosecond6)], Err(TOO_SHORT)); + check(".123456", &[fixed(Nanosecond6)], parsed!(nanosecond: 123_456_000)); + check(".1234567", &[fixed(Nanosecond6)], Err(TOO_LONG)); + check(".1234567", &[fixed(Nanosecond6), Literal("7")], parsed!(nanosecond: 123_456_000)); + + check("", &[fixed(Nanosecond9)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond9)], Err(TOO_LONG)); // never consumes `4` + check(".12345678", &[fixed(Nanosecond9)], Err(TOO_SHORT)); + check(".123456789", &[fixed(Nanosecond9)], parsed!(nanosecond: 123_456_789)); + check(".1234567890", &[fixed(Nanosecond9)], Err(TOO_LONG)); + check(".1234567890", &[fixed(Nanosecond9), Literal("0")], parsed!(nanosecond: 123_456_789)); + + // fixed: nanoseconds without the dot + check("", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("4", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("42", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("421", &[internal_fixed(Nanosecond3NoDot)], parsed!(nanosecond: 421_000_000)); + check("4210", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check( + "42143", + &[internal_fixed(Nanosecond3NoDot), num(Second)], + parsed!(nanosecond: 421_000_000, second: 43), + ); + check( + "421🤠", + &[internal_fixed(Nanosecond3NoDot), Literal("🤠")], + parsed!(nanosecond: 421_000_000), + ); + check( + "🤠421", + &[Literal("🤠"), internal_fixed(Nanosecond3NoDot)], + parsed!(nanosecond: 421_000_000), + ); + check("42195", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + check(".421", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("1234", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("12345", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("421950", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 421_950_000)); + check("000003", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 3000)); + check("000000", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 0)); + check("1234567", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + check(".42100", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("42195", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("12345678", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("421950803", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 421_950_803)); + check("000000003", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 3)); + check( + "42195080354", + &[internal_fixed(Nanosecond9NoDot), num(Second)], + parsed!(nanosecond: 421_950_803, second: 54), + ); // don't skip digits that come after the 9 + check("1234567890", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_LONG)); + check("000000000", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 0)); + check("00000000x", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(" 4", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(".42100000", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); } -} -#[cfg(test)] -#[test] -fn parse_rfc850() { - use {TimeZone, Utc}; - - static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT"; - - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37); - - // Check that the format is what we expect - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); - - // Check that it parses correctly - assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); - - // Check that the rest of the weekdays parse correctly (this test originally failed because - // Sunday parsed incorrectly). - let testdates = [ - (Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 9).and_hms(8, 49, 37), "Wednesday, 09-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 10).and_hms(8, 49, 37), "Thursday, 10-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 11).and_hms(8, 49, 37), "Friday, 11-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 12).and_hms(8, 49, 37), "Saturday, 12-Nov-94 08:49:37 GMT"), - ]; + #[test] + fn test_parse_fixed_timezone_offset() { + use crate::format::Fixed::*; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + + // TimezoneOffset + check("1", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12345", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("-00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("−00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); // MINUS SIGN (U+2212) + check("+00:01", &[fixed(TimezoneOffset)], parsed!(offset: 60)); + check("-00:01", &[fixed(TimezoneOffset)], parsed!(offset: -60)); + check("+00:30", &[fixed(TimezoneOffset)], parsed!(offset: 1_800)); + check("-00:30", &[fixed(TimezoneOffset)], parsed!(offset: -1_800)); + check("+24:00", &[fixed(TimezoneOffset)], parsed!(offset: 86_400)); + check("-24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); + check("−24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); // MINUS SIGN (U+2212) + check("+99:59", &[fixed(TimezoneOffset)], parsed!(offset: 359_940)); + check("-99:59", &[fixed(TimezoneOffset)], parsed!(offset: -359_940)); + check("+00:60", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("+00:99", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("#12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check(" −12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("\t -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("12:34 ", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check( + "+12345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffset), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("Z+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X−12:34", &[fixed(TimezoneOffset)], Err(INVALID)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34🤠", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:🤠34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[Literal("🤠"), fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("A", &[fixed(TimezoneOffset)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffset)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffset)], Err(INVALID)); + check("z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffset)], Err(INVALID)); + + // TimezoneOffsetColon + check("1", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("−12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("#1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check(":", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check( + "+12345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetColon), Literal(":")], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("A", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check("1", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12::34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12:3456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check( + "+12345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetZ), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("X12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("\u{0363}Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("A", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("-Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+A", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+🙃", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" +Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check(" -Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Y", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("+1234ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + check("+12:34ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check("1", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+123", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:7", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:78", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" 12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check(" +12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check(" -12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check(" −12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check( + "+12345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:34:", + &[internal_fixed(TimezoneOffsetPermissive), Literal(":")], + parsed!(offset: 45_240), + ); + check("🤠+12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34🤠", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:🤠34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check( + "+12:34🤠", + &[internal_fixed(TimezoneOffsetPermissive), Literal("🤠")], + parsed!(offset: 45_240), + ); + check( + "🤠+12:34", + &[Literal("🤠"), internal_fixed(TimezoneOffsetPermissive)], + parsed!(offset: 45_240), + ); + check("Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("A", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("Z ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("#Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("-Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+A", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+🙃", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" :Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" +Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check(" -Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+:Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("Y", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + + // TimezoneName + check("CEST", &[fixed(TimezoneName)], parsed!()); + check("cest", &[fixed(TimezoneName)], parsed!()); // lowercase + check("XXXXXXXX", &[fixed(TimezoneName)], parsed!()); // not a real timezone name + check("!!!!", &[fixed(TimezoneName)], parsed!()); // not a real timezone name! + check("CEST 5", &[fixed(TimezoneName), Literal(" "), num(Numeric::Day)], parsed!(day: 5)); + check("CEST ", &[fixed(TimezoneName)], Err(TOO_LONG)); + check(" CEST", &[fixed(TimezoneName)], Err(TOO_LONG)); + check("CE ST", &[fixed(TimezoneName)], Err(TOO_LONG)); + } + + #[test] + #[rustfmt::skip] + fn test_parse_practical_examples() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; + + // some practical examples + check( + "2015-02-04T14:37:05+09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: 32400 + ), + ); + check( + "2015-02-04T14:37:05-09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "20150204143705567", + &[ + num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second), + internal_fixed(Nanosecond3NoDot) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, nanosecond: 567000000 + ), + ); + check( + "Mon, 10 Jun 2013 09:32:37 GMT", + &[ + fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), + Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", + &[ + Literal("🤠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), + Space(" "), fixed(Fixed::ShortMonthName), Literal("🤠"), num(Year), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), + Literal("GMT"), Literal("🤠") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "Sun Aug 02 13:39:15 CEST 2020", + &[ + fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), + Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), + Literal(":"), num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), + num(Year) + ], + parsed!( + year: 2020, month: 8, day: 2, weekday: Weekday::Sun, + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15 + ), + ); + check( + "20060102150405", + &[num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)], + parsed!( + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5 + ), + ); + check( + "3:14PM", + &[num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)], + parsed!(hour_div_12: 1, hour_mod_12: 3, minute: 14), + ); + check( + "12345678901234.56789", + &[num(Timestamp), Literal("."), num(Nanosecond)], + parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234), + ); + check( + "12345678901234.56789", + &[num(Timestamp), fixed(Fixed::Nanosecond)], + parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234), + ); - for val in &testdates { - assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); + // docstring examples from `impl str::FromStr` + check( + "2000-01-02T03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + check( + "2000-01-02 03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); } -} -#[cfg(test)] -#[test] -fn test_rfc3339() { - use super::*; - use offset::FixedOffset; - use DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset - ]; + #[track_caller] + fn parses(s: &str, items: &[Item]) { + let mut parsed = Parsed::new(); + assert!(parse(&mut parsed, s, items.iter()).is_ok()); + } - fn rfc3339_to_datetime(date: &str) -> ParseResult> { + #[track_caller] + fn check(s: &str, items: &[Item], expected: ParseResult) { let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; - parsed.to_datetime() + let result = parse(&mut parsed, s, items.iter()); + let parsed = result.map(|_| parsed); + assert_eq!(parsed, expected); + } + + #[test] + fn test_rfc2822() { + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; + + // Test data - (input, Ok(expected result) or Err(error code)) + let testdates = [ + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment + ( + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // complex trailing comment + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment + ("20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second + ("11 Sep 2001 09:45:00 +0000", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("11 Sep 2001 09:45:00 EST", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), + ("11 Sep 2001 09:45:00 GMT", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month + ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields + ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name + ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour + ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour + ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute + ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset + ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) + // named timezones that have specific timezone offsets + // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -4))), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + // named single-letter military timezones must fallback to +0000 + ("Tue, 20 Jan 2015 17:35:20 Z", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 A", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 a", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + // named single-letter timezone "J" is specifically not valid + ("Tue, 20 Jan 2015 17:35:20 J", Err(INVALID)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(INVALID)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! + ]; + + fn rfc2822_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; + parsed.to_datetime() + } + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + #[cfg(feature = "std")] + eprintln!("Test input: {date:?}\n Expect: {checkdate:?}"); + let dt = rfc2822_to_datetime(date); // parse a date + if dt != checkdate { + // check for expected result + panic!( + "Date conversion failed for {date}\nReceived: {dt:?}\nExpected: {checkdate:?}" + ); + } + } } - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() + #[test] + fn parse_rfc850() { + static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; + + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); + + // Check that the format is what we expect + #[cfg(feature = "alloc")] + assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); + + // Check that it parses correctly + assert_eq!( + NaiveDateTime::parse_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT), + Ok(dt.naive_utc()) + ); + + // Check that the rest of the weekdays parse correctly (this test originally failed because + // Sunday parsed incorrectly). + let testdates = [ + ( + Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), + "Monday, 07-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), + "Tuesday, 08-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), + "Friday, 11-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), + ]; + + for val in &testdates { + assert_eq!(NaiveDateTime::parse_from_str(val.1, RFC850_FMT), Ok(val.0.naive_utc())); + } + + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; + + for val in &test_dates_fail { + assert!(NaiveDateTime::parse_from_str(val, RFC850_FMT).is_err()); + } } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison + #[test] + fn test_rfc3339() { + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + + // Test data - (input, Ok(expected result) or Err(error code)) + let testdates = [ + ("2015-01-20T17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("2015-01-20T17:35:20−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok(ymd_hmsn(1944, 6, 6, 4, 4, 0, 0, 0))), // D-day + ("2001-09-11T09:45:00-08:00", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -8))), + ("2015-01-20T17:35:20.001-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), + ("2015-01-20T17:35:20.001−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 31_000, -8))), + ("2015-01-20T17:35:20.000000004-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), + ("2015-01-20T17:35:20.000000004−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), // with MINUS SIGN (U+2212) + ( + "2015-01-20T17:35:20.000000000452-08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small + ( + "2015-01-20T17:35:20.000000000452−08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small with MINUS SIGN (U+2212) + ("2023-11-05T01:30:00-04:00", Ok(ymd_hmsn(2023, 11, 5, 1, 30, 0, 0, -4))), // ambiguous timestamp + ("2015-01-20 17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T' + ("2015-01-20_17:35:20-08:00", Err(INVALID)), // wrong date time separator + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YM + ("2015-01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char MD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HM + ("2015-01-20T17-35:20.001-08:00", Err(INVALID)), // wrong separator char MS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-00-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad month value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20−08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ]; + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + let dt = DateTime::::parse_from_rfc3339(date); + if dt != checkdate { + // check for expected result + panic!( + "Date conversion failed for {date}\nReceived: {dt:?}\nExpected: {checkdate:?}" + ); + } } } + + #[test] + fn test_issue_1010() { + let dt = crate::NaiveDateTime::parse_from_str( + "\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a", + ); + assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + } } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index cebbafcbd5..3746ba4d97 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,109 +4,173 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; - -use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; -use div::div_rem; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use DateTime; -use Weekday; -use {Datelike, Timelike}; - -/// Parsed parts of date and time. There are two classes of methods: +use super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE, ParseResult}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; +use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; + +/// A type to hold parsed fields of date and time that can check all fields are consistent. +/// +/// There are three classes of methods: /// -/// - `set_*` methods try to set given field(s) while checking for the consistency. -/// It may or may not check for the range constraint immediately (for efficiency reasons). +/// - `set_*` methods to set fields you have available. They do a basic range check, and if the +/// same field is set more than once it is checked for consistency. /// /// - `to_*` methods try to make a concrete date and time value out of set fields. -/// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[allow(missing_copy_implementations)] -#[derive(Clone, PartialEq, Debug, Default)] +/// They fully check that all fields are consistent and whether the date/datetime exists. +/// +/// - Methods to inspect the parsed fields. +/// +/// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it +/// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the +/// results of a string parsed with chrono without converting it to concrete types. +/// +/// # Resolving algorithm +/// +/// Resolving date/time parts is littered with lots of corner cases, which is why common date/time +/// parsers do not implement it correctly. +/// +/// Chrono provides a complete resolution algorithm that checks all fields for consistency via the +/// `Parsed` type. +/// +/// As an easy example, consider RFC 2822. The [RFC 2822 date and time format] has a day of the week +/// part, which should be consistent with the other date parts. But a `strptime`-based parse would +/// happily accept inconsistent input: +/// +/// ```python +/// >>> import time +/// >>> time.strptime('Wed, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=2, tm_yday=365, tm_isdst=-1) +/// >>> time.strptime('Thu, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=3, tm_yday=365, tm_isdst=-1) +/// ``` +/// +/// [RFC 2822 date and time format]: https://tools.ietf.org/html/rfc2822#section-3.3 +/// +/// # Example +/// +/// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent. +/// +/// ``` +/// # #[cfg(feature = "alloc")] { +/// use chrono::format::{ParseErrorKind, Parsed}; +/// use chrono::Weekday; +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Wed)?; +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let dt = parsed.to_datetime()?; +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if let Err(error) = result { +/// assert_eq!(error.kind(), ParseErrorKind::Impossible); +/// } +/// # } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +/// +/// The same using chrono's built-in parser for RFC 2822 (the [RFC2822 formatting item]) and +/// [`format::parse()`] showing how to inspect a field on failure. +/// +/// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 +/// [`format::parse()`]: crate::format::parse() +/// +/// ``` +/// # #[cfg(feature = "alloc")] { +/// use chrono::format::{parse, Fixed, Item, Parsed}; +/// use chrono::Weekday; +/// +/// let rfc_2822 = [Item::Fixed(Fixed::RFC2822)]; +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let dt = parsed.to_datetime()?; +/// +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if result.is_err() { +/// // What is the weekday? +/// assert_eq!(parsed.weekday(), Some(Weekday::Thu)); +/// } +/// # } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +#[allow(clippy::manual_non_exhaustive)] +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Parsed { - /// Year. - /// - /// This can be negative unlike [`year_div_100`](#structfield.year_div_100) - /// and [`year_mod_100`](#structfield.year_mod_100) fields. + #[doc(hidden)] pub year: Option, - - /// Year divided by 100. Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`year_mod_100`](#structfield.year_mod_100) is present, - /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub year_div_100: Option, - - /// Year modulo 100. Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub year_mod_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). - /// - /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. + #[doc(hidden)] pub isoyear: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100. - /// Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present, - /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub isoyear_div_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100. - /// Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub isoyear_mod_100: Option, - - /// Month (1--12). + #[doc(hidden)] + pub quarter: Option, + #[doc(hidden)] pub month: Option, - - /// Week number, where the week 1 starts at the first Sunday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_sun: Option, - - /// Week number, where the week 1 starts at the first Monday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_mon: Option, - - /// [ISO week number](../naive/struct.NaiveDate.html#week-date) - /// (1--52 or 1--53 depending on the year). + #[doc(hidden)] pub isoweek: Option, - - /// Day of the week. + #[doc(hidden)] pub weekday: Option, - - /// Day of the year (1--365 or 1--366 depending on the year). + #[doc(hidden)] pub ordinal: Option, - - /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). + #[doc(hidden)] pub day: Option, - - /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. + #[doc(hidden)] pub hour_div_12: Option, - - /// Hour number modulo 12 (0--11). + #[doc(hidden)] pub hour_mod_12: Option, - - /// Minute number (0--59). + #[doc(hidden)] pub minute: Option, - - /// Second number (0--60, accounting for leap seconds). + #[doc(hidden)] pub second: Option, - - /// The number of nanoseconds since the whole second (0--999,999,999). + #[doc(hidden)] pub nanosecond: Option, - - /// The number of non-leap seconds since the midnight UTC on January 1, 1970. - /// - /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second). + #[doc(hidden)] pub timestamp: Option, - - /// Offset from the local time to UTC, in seconds. + #[doc(hidden)] pub offset: Option, - - /// A dummy field to make this type not fully destructible (required for API stability). + #[doc(hidden)] _dummy: (), } @@ -114,169 +178,387 @@ pub struct Parsed { /// and if it is empty, set `old` to `new` as well. #[inline] fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<()> { - if let Some(ref old) = *old { - if *old == new { + match old { + Some(old) if *old != new => Err(IMPOSSIBLE), + _ => { + *old = Some(new); Ok(()) - } else { - Err(IMPOSSIBLE) } - } else { - *old = Some(new); - Ok(()) } } impl Parsed { /// Returns the initial value of parsed parts. + #[must_use] pub fn new() -> Parsed { Parsed::default() } - /// Tries to set the [`year`](#structfield.year) field from given value. + /// Set the [`year`](Parsed::year) field to the given value. + /// + /// The value can be negative, unlike the [`year_div_100`](Parsed::year_div_100) and + /// [`year_mod_100`](Parsed::year_mod_100) fields. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.year, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value. + /// Set the [`year_div_100`](Parsed::year_div_100) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_div_100, value as i32) } - /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value. + /// Set the [`year_mod_100`](Parsed::year_mod_100) field to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the [`year_div_100`](Parsed::year_div_100) field is missing (and + /// the full [`year`](Parsed::year) field is also not set), it assumes a default value for the + /// [`year_div_100`](Parsed::year_div_100) field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_mod_100, value as i32) } - /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value. + /// Set the [`isoyear`](Parsed::isoyear) field, that is part of an [ISO 8601 week date], to the + /// given value. + /// + /// The value can be negative, unlike the [`isoyear_div_100`](Parsed::isoyear_div_100) and + /// [`isoyear_mod_100`](Parsed::isoyear_mod_100) fields. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoyear, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. + /// Set the [`isoyear_div_100`](Parsed::isoyear_div_100) field, that is part of an + /// [ISO 8601 week date], to the given value. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear_div_100, value as i32) } - /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value. + /// Set the [`isoyear_mod_100`](Parsed::isoyear_mod_100) field, that is part of an + /// [ISO 8601 week date], to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the [`isoyear_div_100`](Parsed::isoyear_div_100) field is missing + /// (and the full [`isoyear`](Parsed::isoyear) field is also not set), it assumes a default + /// value for the [`isoyear_div_100`](Parsed::isoyear_div_100) field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear_mod_100, value as i32) } - /// Tries to set the [`month`](#structfield.month) field from given value. + /// Set the [`quarter`](Parsed::quarter) field to the given value. + /// + /// Quarter 1 starts in January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-4. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_quarter(&mut self, value: i64) -> ParseResult<()> { + if !(1..=4).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.quarter, value as u32) + } + + /// Set the [`month`](Parsed::month) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_month(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.month, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=12).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.month, value as u32) } - /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value. + /// Set the [`week_from_sun`](Parsed::week_from_sun) week number field to the given value. + /// + /// Week 1 starts at the first Sunday of January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_sun, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_sun, value as u32) } - /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value. + /// Set the [`week_from_mon`](Parsed::week_from_mon) week number field to the given value. + /// Set the 'week number starting with Monday' field to the given value. + /// + /// Week 1 starts at the first Monday of January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_mon, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_mon, value as u32) } - /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value. + /// Set the [`isoweek`](Parsed::isoweek) field for an [ISO 8601 week date] to the given value. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoweek, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.isoweek, value as u32) } - /// Tries to set the [`weekday`](#structfield.weekday) field from given value. + /// Set the [`weekday`](Parsed::weekday) field to the given value. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { set_if_consistent(&mut self.weekday, value) } - /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. + /// Set the [`ordinal`](Parsed::ordinal) (day of the year) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-366. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.ordinal, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=366).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.ordinal, value as u32) } - /// Tries to set the [`day`](#structfield.day) field from given value. + /// Set the [`day`](Parsed::day) of the month field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-31. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_day(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.day, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=31).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.day, value as u32) } - /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value. - /// (`false` for AM, `true` for PM) + /// Set the [`hour_div_12`](Parsed::hour_div_12) am/pm field to the given value. + /// + /// `false` indicates AM and `true` indicates PM. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { - set_if_consistent(&mut self.hour_div_12, if value { 1 } else { 0 }) + set_if_consistent(&mut self.hour_div_12, value as u32) } - /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from - /// given hour number in 12-hour clocks. + /// Set the [`hour_mod_12`](Parsed::hour_mod_12) field, for the hour number in 12-hour clocks, + /// to the given value. + /// + /// Value must be in the canonical range of 1-12. + /// It will internally be stored as 0-11 (`value % 12`). + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] - pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { - if value < 1 || value > 12 { + pub fn set_hour12(&mut self, mut value: i64) -> ParseResult<()> { + if !(1..=12).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) + if value == 12 { + value = 0 + } + set_if_consistent(&mut self.hour_mod_12, value as u32) } - /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and - /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. + /// Set the [`hour_div_12`](Parsed::hour_div_12) and [`hour_mod_12`](Parsed::hour_mod_12) + /// fields to the given value for a 24-hour clock. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-23. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` one of the fields was already set to a different value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { - let v = value.to_u32().ok_or(OUT_OF_RANGE)?; - set_if_consistent(&mut self.hour_div_12, v / 12)?; - set_if_consistent(&mut self.hour_mod_12, v % 12)?; - Ok(()) + let (hour_div_12, hour_mod_12) = match value { + hour @ 0..=11 => (0, hour as u32), + hour @ 12..=23 => (1, hour as u32 - 12), + _ => return Err(OUT_OF_RANGE), + }; + set_if_consistent(&mut self.hour_div_12, hour_div_12)?; + set_if_consistent(&mut self.hour_mod_12, hour_mod_12) } - /// Tries to set the [`minute`](#structfield.minute) field from given value. + /// Set the [`minute`](Parsed::minute) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-59. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.minute, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=59).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.minute, value as u32) } - /// Tries to set the [`second`](#structfield.second) field from given value. + /// Set the [`second`](Parsed::second) field to the given value. + /// + /// The value can be 60 in the case of a leap second. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-60. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_second(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.second, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=60).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.second, value as u32) } - /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. + /// Set the [`nanosecond`](Parsed::nanosecond) field to the given value. + /// + /// This is the number of nanoseconds since the whole second. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=999_999_999).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.nanosecond, value as u32) } - /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. + /// Set the [`timestamp`](Parsed::timestamp) field to the given value. + /// + /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on + /// January 1, 1970. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) } - /// Tries to set the [`offset`](#structfield.offset) field from given value. + /// Set the [`offset`](Parsed::offset) field to the given value. + /// + /// The offset is in seconds from local time to UTC. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.offset, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Returns a parsed naive date out of given fields. @@ -290,6 +572,18 @@ impl Parsed { /// /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, /// the two-digit year is used to guess the century number then. + /// + /// It checks all given date fields are consistent with each other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete date. + /// - `OUT_OF_RANGE` + /// - if any of the date fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the value would be outside the range of a [`NaiveDate`]. + /// - if the date does not exist. pub fn to_naive_date(&self) -> ParseResult { fn resolve_year( y: Option, @@ -305,11 +599,12 @@ impl Parsed { // check if present quotient and/or modulo is consistent to the full year. // since the presence of those fields means a positive full year, // we should filter a negative full year first. - (Some(y), q, r @ Some(0...99)) | (Some(y), q, r @ None) => { + (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { if y < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } - let (q_, r_) = div_rem(y, 100); + let q_ = y / 100; + let r_ = y % 100; if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { Ok(Some(y)) } else { @@ -319,9 +614,9 @@ impl Parsed { // the full year is missing but we have quotient and modulo. // reconstruct the full year. make sure that the result is always positive. - (None, Some(q), Some(r @ 0...99)) => { + (None, Some(q), Some(r @ 0..=99)) => { if q < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); Ok(Some(y.ok_or(OUT_OF_RANGE)?)) @@ -329,7 +624,7 @@ impl Parsed { // we only have modulo. try to interpret a modulo as a conventional two-digit year. // note: we are affected by Rust issue #18060. avoid multiple range patterns. - (None, None, Some(r @ 0...99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), + (None, None, Some(r @ 0..=99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), // otherwise it is an out-of-bound or insufficient condition. (None, Some(_), None) => Err(NOT_ENOUGH), @@ -344,8 +639,7 @@ impl Parsed { let verify_ymd = |date: NaiveDate| { let year = date.year(); let (year_div_100, year_mod_100) = if year >= 0 { - let (q, r) = div_rem(year, 100); - (Some(q), Some(r)) + (Some(year / 100), Some(year % 100)) } else { (None, None) // they should be empty to be consistent }; @@ -365,8 +659,7 @@ impl Parsed { let isoweek = week.week(); let weekday = date.weekday(); let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { - let (q, r) = div_rem(isoyear, 100); - (Some(q), Some(r)) + (Some(isoyear / 100), Some(isoyear % 100)) } else { (None, None) // they should be empty to be consistent }; @@ -380,9 +673,8 @@ impl Parsed { // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); - let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 7) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 7) / 7; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon @@ -404,71 +696,15 @@ impl Parsed { (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_sun: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Sunday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 0, - Weekday::Mon => 6, - Weekday::Tue => 5, - Weekday::Wed => 4, - Weekday::Thu => 3, - Weekday::Fri => 2, - Weekday::Sat => 1, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_sun > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_sun as i32 - 1) * 7 - + weekday.num_days_from_sunday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Sun)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_mon: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Monday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 1, - Weekday::Mon => 0, - Weekday::Tue => 6, - Weekday::Wed => 5, - Weekday::Thu => 4, - Weekday::Fri => 3, - Weekday::Sat => 2, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_mon > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_mon as i32 - 1) * 7 - + weekday.num_days_from_monday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Mon)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } @@ -482,11 +718,15 @@ impl Parsed { (_, _, _) => return Err(NOT_ENOUGH), }; - if verified { - Ok(parsed_date) - } else { - Err(IMPOSSIBLE) + if !verified { + return Err(IMPOSSIBLE); + } else if let Some(parsed) = self.quarter { + if parsed != parsed_date.quarter() { + return Err(IMPOSSIBLE); + } } + + Ok(parsed_date) } /// Returns a parsed naive time out of given fields. @@ -498,34 +738,42 @@ impl Parsed { /// - Hour, minute, second, nanosecond. /// /// It is able to handle leap seconds when given second is 60. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if any of the time fields of `Parsed` are set to a value beyond + /// their acceptable range. + /// - `NOT_ENOUGH` if an hour field is missing, if AM/PM is missing in a 12-hour clock, + /// if minutes are missing, or if seconds are missing while the nanosecond field is present. pub fn to_naive_time(&self) -> ParseResult { let hour_div_12 = match self.hour_div_12 { - Some(v @ 0...1) => v, + Some(v @ 0..=1) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour_mod_12 = match self.hour_mod_12 { - Some(v @ 0...11) => v, + Some(v @ 0..=11) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour = hour_div_12 * 12 + hour_mod_12; let minute = match self.minute { - Some(v @ 0...59) => v, + Some(v @ 0..=59) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; // we allow omitting seconds or nanoseconds, but they should be in the range. let (second, mut nano) = match self.second.unwrap_or(0) { - v @ 0...59 => (v, 0), + v @ 0..=59 => (v, 0), 60 => (59, 1_000_000_000), _ => return Err(OUT_OF_RANGE), }; nano += match self.nanosecond { - Some(v @ 0...999_999_999) if self.second.is_some() => v, - Some(0...999_999_999) => return Err(NOT_ENOUGH), // second is missing + Some(v @ 0..=999_999_999) if self.second.is_some() => v, + Some(0..=999_999_999) => return Err(NOT_ENOUGH), // second is missing Some(_) => return Err(OUT_OF_RANGE), None => 0, }; @@ -533,13 +781,25 @@ impl Parsed { NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) } - /// Returns a parsed naive date and time out of given fields, - /// except for the [`offset`](#structfield.offset) field (assumed to have a given value). - /// This is required for parsing a local time or other known-timezone inputs. + /// Returns a parsed naive date and time out of given fields, except for the offset field. + /// + /// The offset is assumed to have a given value. It is not compared against the offset field set + /// in the `Parsed` type, so it is allowed to be inconsistent. + /// + /// This method is able to determine the combined date and time from date and time fields or + /// from a single timestamp field. It checks all fields are consistent with each other. + /// + /// # Errors /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field. - /// Either way those fields have to be consistent to each other. + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime. + /// - `OUT_OF_RANGE` + /// - if any of the date or time fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`]. + /// - if the date does not exist. pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult { let date = self.to_naive_date(); let time = self.to_naive_time(); @@ -548,7 +808,7 @@ impl Parsed { // verify the timestamp field if any // the following is safe, `timestamp` is very limited in range - let timestamp = datetime.timestamp() - i64::from(offset); + let timestamp = datetime.and_utc().timestamp() - i64::from(offset); if let Some(given_timestamp) = self.timestamp { // if `datetime` represents a leap second, it might be off by one second. if given_timestamp != timestamp @@ -573,8 +833,7 @@ impl Parsed { // reconstruct date and time fields from timestamp let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; - let datetime = NaiveDateTime::from_timestamp_opt(ts, 0); - let mut datetime = datetime.ok_or(OUT_OF_RANGE)?; + let mut datetime = DateTime::from_timestamp_secs(ts).ok_or(OUT_OF_RANGE)?.naive_utc(); // fill year, ordinal, hour, minute and second fields from timestamp. // if existing fields are consistent, this will allow the full date/time reconstruction. @@ -586,7 +845,7 @@ impl Parsed { 59 => {} // `datetime` is known to be off by one second. 0 => { - datetime -= OldDuration::seconds(1); + datetime -= TimeDelta::try_seconds(1).unwrap(); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), @@ -613,36 +872,76 @@ impl Parsed { } /// Returns a parsed fixed time zone offset out of given fields. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if the offset is out of range for a `FixedOffset`. + /// - `NOT_ENOUGH` if the offset field is not set. pub fn to_fixed_offset(&self) -> ParseResult { - self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) + FixedOffset::east_opt(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE) } /// Returns a parsed timezone-aware date and time out of given fields. /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. + /// This method is able to determine the combined date and time from date, time and offset + /// fields, and/or from a single timestamp field. It checks all fields are consistent with each + /// other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime + /// including offset from UTC. + /// - `OUT_OF_RANGE` + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if the date does not exist. pub fn to_datetime(&self) -> ParseResult> { - let offset = self.offset.ok_or(NOT_ENOUGH)?; + // If there is no explicit offset, consider a timestamp value as indication of a UTC value. + let offset = match (self.offset, self.timestamp) { + (Some(off), _) => off, + (None, Some(_)) => 0, // UNIX timestamp may assume 0 offset + (None, None) => return Err(NOT_ENOUGH), + }; let datetime = self.to_naive_datetime_with_offset(offset)?; let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + match offset.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => Ok(t), - LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => Ok(t), + MappedLocalTime::Ambiguous(..) => Err(NOT_ENOUGH), } } /// Returns a parsed timezone-aware date and time out of given fields, - /// with an additional `TimeZone` used to interpret and validate the local date. - /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. - /// If parsed fields include an UTC offset, it also has to be consistent to - /// [`offset`](#structfield.offset). + /// with an additional [`TimeZone`] used to interpret and validate the local date. + /// + /// This method is able to determine the combined date and time from date and time, and/or from + /// a single timestamp field. It checks all fields are consistent with each other. + /// + /// If the parsed fields include an UTC offset, it also has to be consistent with the offset in + /// the provided `tz` time zone for that datetime. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` + /// - if any of the date fields conflict, if a timestamp conflicts with any of the other + /// fields, or if the offset field is set but differs from the offset at that time in the + /// `tz` time zone. + /// - if the local datetime does not exists in the provided time zone (because it falls in a + /// transition due to for example DST). + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime, or if + /// the local time in the provided time zone is ambiguous (because it falls in a transition + /// due to for example DST) while there is no offset field or timestamp field set. + /// - `OUT_OF_RANGE` + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the date does not exist. pub fn to_datetime_with_timezone(&self, tz: &Tz) -> ParseResult> { // if we have `timestamp` specified, guess an offset from that. let mut guessed_offset = 0; @@ -650,8 +949,8 @@ impl Parsed { // make a naive `DateTime` from given timestamp and (if any) nanosecond. // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. let nanosecond = self.nanosecond.unwrap_or(0); - let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond); - let dt = dt.ok_or(OUT_OF_RANGE)?; + let dt = + DateTime::from_timestamp(timestamp, nanosecond).ok_or(OUT_OF_RANGE)?.naive_utc(); guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); } @@ -668,15 +967,15 @@ impl Parsed { // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; match tz.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => { + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => { if check_offset(&t) { Ok(t) } else { Err(IMPOSSIBLE) } } - LocalResult::Ambiguous(min, max) => { + MappedLocalTime::Ambiguous(min, max) => { // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), @@ -687,16 +986,219 @@ impl Parsed { } } } + + /// Get the `year` field if set. + /// + /// See also [`set_year()`](Parsed::set_year). + #[inline] + pub fn year(&self) -> Option { + self.year + } + + /// Get the `year_div_100` field if set. + /// + /// See also [`set_year_div_100()`](Parsed::set_year_div_100). + #[inline] + pub fn year_div_100(&self) -> Option { + self.year_div_100 + } + + /// Get the `year_mod_100` field if set. + /// + /// See also [`set_year_mod_100()`](Parsed::set_year_mod_100). + #[inline] + pub fn year_mod_100(&self) -> Option { + self.year_mod_100 + } + + /// Get the `isoyear` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear()`](Parsed::set_isoyear). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear(&self) -> Option { + self.isoyear + } + + /// Get the `isoyear_div_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_div_100()`](Parsed::set_isoyear_div_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_div_100(&self) -> Option { + self.isoyear_div_100 + } + + /// Get the `isoyear_mod_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_mod_100()`](Parsed::set_isoyear_mod_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_mod_100(&self) -> Option { + self.isoyear_mod_100 + } + + /// Get the `quarter` field if set. + /// + /// See also [`set_quarter()`](Parsed::set_quarter). + #[inline] + pub fn quarter(&self) -> Option { + self.quarter + } + + /// Get the `month` field if set. + /// + /// See also [`set_month()`](Parsed::set_month). + #[inline] + pub fn month(&self) -> Option { + self.month + } + + /// Get the `week_from_sun` field if set. + /// + /// See also [`set_week_from_sun()`](Parsed::set_week_from_sun). + #[inline] + pub fn week_from_sun(&self) -> Option { + self.week_from_sun + } + + /// Get the `week_from_mon` field if set. + /// + /// See also [`set_week_from_mon()`](Parsed::set_week_from_mon). + #[inline] + pub fn week_from_mon(&self) -> Option { + self.week_from_mon + } + + /// Get the `isoweek` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoweek()`](Parsed::set_isoweek). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoweek(&self) -> Option { + self.isoweek + } + + /// Get the `weekday` field if set. + /// + /// See also [`set_weekday()`](Parsed::set_weekday). + #[inline] + pub fn weekday(&self) -> Option { + self.weekday + } + + /// Get the `ordinal` (day of the year) field if set. + /// + /// See also [`set_ordinal()`](Parsed::set_ordinal). + #[inline] + pub fn ordinal(&self) -> Option { + self.ordinal + } + + /// Get the `day` of the month field if set. + /// + /// See also [`set_day()`](Parsed::set_day). + #[inline] + pub fn day(&self) -> Option { + self.day + } + + /// Get the `hour_div_12` field (am/pm) if set. + /// + /// 0 indicates AM and 1 indicates PM. + /// + /// See also [`set_ampm()`](Parsed::set_ampm) and [`set_hour()`](Parsed::set_hour). + #[inline] + pub fn hour_div_12(&self) -> Option { + self.hour_div_12 + } + + /// Get the `hour_mod_12` field if set. + /// + /// See also [`set_hour12()`](Parsed::set_hour12) and [`set_hour()`](Parsed::set_hour). + pub fn hour_mod_12(&self) -> Option { + self.hour_mod_12 + } + + /// Get the `minute` field if set. + /// + /// See also [`set_minute()`](Parsed::set_minute). + #[inline] + pub fn minute(&self) -> Option { + self.minute + } + + /// Get the `second` field if set. + /// + /// See also [`set_second()`](Parsed::set_second). + #[inline] + pub fn second(&self) -> Option { + self.second + } + + /// Get the `nanosecond` field if set. + /// + /// See also [`set_nanosecond()`](Parsed::set_nanosecond). + #[inline] + pub fn nanosecond(&self) -> Option { + self.nanosecond + } + + /// Get the `timestamp` field if set. + /// + /// See also [`set_timestamp()`](Parsed::set_timestamp). + #[inline] + pub fn timestamp(&self) -> Option { + self.timestamp + } + + /// Get the `offset` field if set. + /// + /// See also [`set_offset()`](Parsed::set_offset). + #[inline] + pub fn offset(&self) -> Option { + self.offset + } +} + +/// Create a `NaiveDate` when given a year, week, weekday, and the definition at which day of the +/// week a week starts. +/// +/// Returns `IMPOSSIBLE` if `week` is `0` or `53` and the `weekday` falls outside the year. +fn resolve_week_date( + year: i32, + week: u32, + weekday: Weekday, + week_start_day: Weekday, +) -> ParseResult { + if week > 53 { + return Err(OUT_OF_RANGE); + } + + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; + // Ordinal of the day at which week 1 starts. + let first_week_start = 1 + week_start_day.days_since(first_day_of_year.weekday()) as i32; + // Number of the `weekday`, which is 0 for the first day of the week. + let weekday = weekday.days_since(week_start_day) as i32; + let ordinal = first_week_start + (week as i32 - 1) * 7 + weekday; + if ordinal <= 0 { + return Err(IMPOSSIBLE); + } + first_day_of_year.with_ordinal(ordinal as u32).ok_or(IMPOSSIBLE) } #[cfg(test)] mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; - use naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE}; - use offset::{FixedOffset, TimeZone, Utc}; - use Datelike; - use Weekday::*; + use crate::Datelike; + use crate::Weekday::*; + use crate::naive::{NaiveDate, NaiveTime}; + use crate::offset::{FixedOffset, TimeZone, Utc}; #[test] fn test_parsed_set_fields() { @@ -769,6 +1271,107 @@ mod tests { assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); } + #[test] + fn test_parsed_set_range() { + assert_eq!(Parsed::new().set_year(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_year(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_div_100(0).is_ok()); + assert!(Parsed::new().set_year_div_100(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_mod_100(0).is_ok()); + assert!(Parsed::new().set_year_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_year_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_isoyear(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_isoyear(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_div_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_div_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_mod_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_quarter(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_quarter(1).is_ok()); + assert!(Parsed::new().set_quarter(4).is_ok()); + assert_eq!(Parsed::new().set_quarter(5), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_month(1).is_ok()); + assert!(Parsed::new().set_month(12).is_ok()); + assert_eq!(Parsed::new().set_month(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_sun(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_sun(0).is_ok()); + assert!(Parsed::new().set_week_from_sun(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_sun(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_mon(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_mon(0).is_ok()); + assert!(Parsed::new().set_week_from_mon(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_mon(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoweek(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoweek(1).is_ok()); + assert!(Parsed::new().set_isoweek(53).is_ok()); + assert_eq!(Parsed::new().set_isoweek(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_ordinal(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_ordinal(1).is_ok()); + assert!(Parsed::new().set_ordinal(366).is_ok()); + assert_eq!(Parsed::new().set_ordinal(367), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_day(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_day(1).is_ok()); + assert!(Parsed::new().set_day(31).is_ok()); + assert_eq!(Parsed::new().set_day(32), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour12(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour12(1).is_ok()); + assert!(Parsed::new().set_hour12(12).is_ok()); + assert_eq!(Parsed::new().set_hour12(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour(0).is_ok()); + assert!(Parsed::new().set_hour(23).is_ok()); + assert_eq!(Parsed::new().set_hour(24), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_minute(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_minute(0).is_ok()); + assert!(Parsed::new().set_minute(59).is_ok()); + assert_eq!(Parsed::new().set_minute(60), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_second(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_second(0).is_ok()); + assert!(Parsed::new().set_second(60).is_ok()); + assert_eq!(Parsed::new().set_second(61), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_nanosecond(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_nanosecond(0).is_ok()); + assert!(Parsed::new().set_nanosecond(999_999_999).is_ok()); + assert_eq!(Parsed::new().set_nanosecond(1_000_000_000), Err(OUT_OF_RANGE)); + + assert!(Parsed::new().set_timestamp(i64::MIN).is_ok()); + assert!(Parsed::new().set_timestamp(i64::MAX).is_ok()); + + assert_eq!(Parsed::new().set_offset(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_offset(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_offset(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_offset(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + } + #[test] fn test_parsed_to_naive_date() { macro_rules! parse { @@ -777,7 +1380,7 @@ mod tests { ) } - let ymd = |y, m, d| Ok(NaiveDate::from_ymd(y, m, d)); + let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); // ymd: omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -822,8 +1425,8 @@ mod tests { ); assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); - assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE)); - let max_year = MAX_DATE.year(); + assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(IMPOSSIBLE)); + let max_year = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, year_mod_100: max_year % 100, month: 1, day: 1), @@ -858,17 +1461,28 @@ mod tests { ); assert_eq!( parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), - Err(OUT_OF_RANGE) + Err(IMPOSSIBLE) ); - assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE)); + + // quarters + assert_eq!(parse!(year: 2000, quarter: 1), Err(NOT_ENOUGH)); + assert_eq!(parse!(year: 2000, quarter: 1, month: 1, day: 1), ymd(2000, 1, 1)); + assert_eq!(parse!(year: 2000, quarter: 2, month: 4, day: 1), ymd(2000, 4, 1)); + assert_eq!(parse!(year: 2000, quarter: 3, month: 7, day: 1), ymd(2000, 7, 1)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 10, day: 1), ymd(2000, 10, 1)); + + // quarter: conflicting inputs + assert_eq!(parse!(year: 2000, quarter: 2, month: 3, day: 31), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 3, day: 31), Err(IMPOSSIBLE)); // weekdates assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); - assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); @@ -882,9 +1496,9 @@ mod tests { assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); - assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); // weekdates: conflicting inputs @@ -964,8 +1578,8 @@ mod tests { ) } - let hms = |h, m, s| Ok(NaiveTime::from_hms(h, m, s)); - let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano(h, m, s, n)); + let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); + let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1020,9 +1634,12 @@ mod tests { ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) } - let ymdhms = |y, m, d, h, n, s| Ok(NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - let ymdhmsn = - |y, m, d, h, n, s, nano| Ok(NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano)); + let ymdhms = |y, m, d, h, n, s| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) + }; + let ymdhmsn = |y, m, d, h, n, s, nano| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) + }; // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1076,14 +1693,15 @@ mod tests { // more timestamps let max_days_from_year_1970 = - MAX_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); - let year_0_from_year_1970 = - NaiveDate::from_ymd(0, 1, 1).signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let min_days_from_year_1970 = - MIN_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); assert_eq!( parse!(timestamp: min_days_from_year_1970.num_seconds()), - ymdhms(MIN_DATE.year(), 1, 1, 0, 0, 0) + ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: year_0_from_year_1970.num_seconds()), @@ -1091,7 +1709,7 @@ mod tests { ); assert_eq!( parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), - ymdhms(MAX_DATE.year(), 12, 31, 23, 59, 59) + ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); // leap seconds #1: partial fields @@ -1170,7 +1788,15 @@ mod tests { } let ymdhmsn = |y, m, d, h, n, s, nano, off| { - Ok(FixedOffset::east(off).ymd(y, m, d).and_hms_nano(h, n, s, nano)) + Ok(FixedOffset::east_opt(off) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_nano_opt(h, n, s, nano) + .unwrap(), + ) + .unwrap()) }; assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); @@ -1214,7 +1840,14 @@ mod tests { parse!(Utc; year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms_nano(4, 26, 40, 12_345_678)) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(4, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); assert_eq!( parse!(Utc; @@ -1223,33 +1856,58 @@ mod tests { Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms_nano(13, 26, 40, 12_345_678)) + Ok(FixedOffset::east_opt(32400) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(13, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); // single result from timestamp assert_eq!( parse!(Utc; timestamp: 1_420_000_000, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms(4, 26, 40)) + Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) ); assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 0), + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms(13, 26, 40)) + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), + Ok(FixedOffset::east_opt(32400) + .unwrap() + .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) + .unwrap()) ); // TODO test with a variable time zone (for None and Ambiguous cases) } + + #[test] + fn issue_551() { + use crate::Weekday; + let mut parsed = Parsed::new(); + + parsed.year = Some(2002); + parsed.week_from_mon = Some(22); + parsed.weekday = Some(Weekday::Mon); + assert_eq!(NaiveDate::from_ymd_opt(2002, 6, 3).unwrap(), parsed.to_naive_date().unwrap()); + + parsed.year = Some(2001); + assert_eq!(NaiveDate::from_ymd_opt(2001, 5, 28).unwrap(), parsed.to_naive_date().unwrap()); + } } diff --git a/src/format/scan.rs b/src/format/scan.rs index 581ed4ef73..30dbc6746b 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -5,28 +5,8 @@ * Various scanning routines for the parser. */ -#![allow(deprecated)] - -use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; -use Weekday; - -/// Returns true when two slices are equal case-insensitively (in ASCII). -/// Assumes that the `pattern` is already converted to lower case. -fn equals(s: &str, pattern: &str) -> bool { - let mut xs = s.as_bytes().iter().map(|&c| match c { - b'A'...b'Z' => c + 32, - _ => c, - }); - let mut ys = pattern.as_bytes().iter().cloned(); - loop { - match (xs.next(), ys.next()) { - (None, None) => return true, - (None, _) | (_, None) => return false, - (Some(x), Some(y)) if x != y => return false, - _ => (), - } - } -} +use super::{INVALID, OUT_OF_RANGE, ParseResult, TOO_SHORT}; +use crate::Weekday; /// Tries to parse the non-negative number from `min` to `max` digits. /// @@ -34,7 +14,7 @@ fn equals(s: &str, pattern: &str) -> bool { /// More than `max` digits are consumed up to the first `max` digits. /// Any number that does not fit in `i64` is an error. #[inline] -pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { +pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { assert!(min <= max); // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on @@ -48,7 +28,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if c < b'0' || b'9' < c { + if !c.is_ascii_digit() { if i < min { return Err(INVALID); } else { @@ -62,31 +42,32 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { }; } - Ok((&s[::core::cmp::min(max, bytes.len())..], n)) + Ok((&s[core::cmp::min(max, bytes.len())..], n)) } /// Tries to consume at least one digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, u32)> { // record the number of digits consumed for later scaling. let origlen = s.len(); let (s, v) = number(s, 1, 9)?; + let v = u32::try_from(v).expect("999,999,999 should fit u32"); let consumed = origlen - s.len(); // scale the number accordingly. - static SCALE: [i64; 10] = + const SCALE: [u32; 10] = [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1]; let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9'); + let s = s.trim_start_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } /// Tries to consume a fixed number of digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let (s, v) = number(s, digits, digits)?; @@ -99,7 +80,7 @@ pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { } /// Tries to parse the month index (0 through 11) with the first three ASCII letters. -pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -123,7 +104,7 @@ pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { } /// Tries to parse the weekday with the first three ASCII letters. -pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -143,16 +124,18 @@ pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// Tries to parse the month index (0 through 11) with short or long month names. /// It prefers long month names to short month names when both are possible. -pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&'static str; 12] = - ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; + static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [ + b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember", + b"ember", + ]; let (mut s, month0) = short_month0(s)?; // tries to consume the suffix if possible let suffix = LONG_MONTH_SUFFIXES[month0 as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -161,16 +144,16 @@ pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// Tries to parse the weekday with short or long weekday names. /// It prefers long weekday names to short weekday names when both are possible. -pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] = - ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; + static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] = + [b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"]; let (mut s, weekday) = short_weekday(s)?; // tries to consume the suffix if possible let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -178,7 +161,7 @@ pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { } /// Tries to consume exactly one given character. -pub fn char(s: &str, c1: u8) -> ParseResult<&str> { +pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { match s.as_bytes().first() { Some(&c) if c == c1 => Ok(&s[1..]), Some(_) => Err(INVALID), @@ -187,8 +170,8 @@ pub fn char(s: &str, c1: u8) -> ParseResult<&str> { } /// Tries to consume one or more whitespace. -pub fn space(s: &str) -> ParseResult<&str> { - let s_ = s.trim_left(); +pub(super) fn space(s: &str) -> ParseResult<&str> { + let s_ = s.trim_start(); if s_.len() < s.len() { Ok(s_) } else if s.is_empty() { @@ -199,48 +182,73 @@ pub fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) +pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { + Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } -/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. +/// Parse a timezone from `s` and return the offset in seconds. /// -/// The additional `colon` may be used to parse a mandatory or optional `:` -/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset_internal(s, consume_colon, false) -} - -fn timezone_offset_internal( +/// The `consume_colon` function is used to parse a mandatory or optional `:` +/// separator between hours offset and minutes offset. +/// +/// The `allow_missing_minutes` flag allows the timezone minutes offset to be +/// missing from `s`. +/// +/// The `allow_tz_minus_sign` flag allows the timezone offset negative character +/// to also be `−` MINUS SIGN (U+2212) in addition to the typical +/// ASCII-compatible `-` HYPHEN-MINUS (U+2D). +/// This is part of [RFC 3339 & ISO 8601]. +/// +/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC +pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, + allow_zulu: bool, allow_missing_minutes: bool, + allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { - fn digits(s: &str) -> ParseResult<(u8, u8)> { - let b = s.as_bytes(); - if b.len() < 2 { - Err(TOO_SHORT) - } else { - Ok((b[0], b[1])) + if allow_zulu { + if let Some(&b'Z' | &b'z') = s.as_bytes().first() { + return Ok((&s[1..], 0)); } } - let negative = match s.as_bytes().first() { - Some(&b'+') => false, - Some(&b'-') => true, + + const fn digits(s: &str) -> ParseResult<(u8, u8)> { + let b = s.as_bytes(); + if b.len() < 2 { Err(TOO_SHORT) } else { Ok((b[0], b[1])) } + } + let negative = match s.chars().next() { + Some('+') => { + // PLUS SIGN (U+2B) + s = &s['+'.len_utf8()..]; + + false + } + Some('-') => { + // HYPHEN-MINUS (U+2D) + s = &s['-'.len_utf8()..]; + + true + } + Some('−') => { + // MINUS SIGN (U+2212) + if !allow_tz_minus_sign { + return Err(INVALID); + } + s = &s['−'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; - s = &s[1..]; // hours (00--99) let hours = match digits(s)? { - (h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), + (h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), _ => return Err(INVALID), }; s = &s[2..]; @@ -252,8 +260,8 @@ where // if the next two items are digits then we have to add minutes let minutes = if let Ok(ds) = digits(s) { match ds { - (m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), - (b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE), + (m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), + (b'6'..=b'9', b'0'..=b'9') => return Err(OUT_OF_RANGE), _ => return Err(INVALID), } } else if allow_missing_minutes { @@ -263,7 +271,7 @@ where }; s = match s.len() { len if len >= 2 => &s[2..], - len if len == 0 => s, + 0 => s, _ => return Err(TOO_SHORT), }; @@ -271,80 +279,154 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - let bytes = s.as_bytes(); - match bytes.first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - Some(&b'u') | Some(&b'U') => { - if bytes.len() >= 3 { - let (b, c) = (bytes[1], bytes[2]); - match (b | 32, c | 32) { - (b't', b'c') => Ok((&s[3..], 0)), - _ => Err(INVALID), - } - } else { - Err(INVALID) +/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. +/// May return `None` which indicates an insufficient offset data (i.e. `-0000`). +/// See [RFC 2822 Section 4.3]. +/// +/// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> { + // tries to parse legacy time zone names + let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len()); + if upto > 0 { + let name = &s.as_bytes()[..upto]; + let s = &s[upto..]; + let offset_hours = |o| Ok((s, o * 3600)); + // RFC 2822 requires support for some named North America timezones, a small subset of all + // named timezones. + if name.eq_ignore_ascii_case(b"gmt") + || name.eq_ignore_ascii_case(b"ut") + || name.eq_ignore_ascii_case(b"z") + { + return offset_hours(0); + } else if name.eq_ignore_ascii_case(b"edt") { + return offset_hours(-4); + } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { + return offset_hours(-5); + } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { + return offset_hours(-6); + } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { + return offset_hours(-7); + } else if name.eq_ignore_ascii_case(b"pst") { + return offset_hours(-8); + } else if name.len() == 1 { + if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] { + // recommended by RFC 2822: consume but treat it as -0000 + return Ok((s, 0)); } } - _ => timezone_offset(s, colon), + Err(INVALID) + } else { + timezone_offset(s, |s| Ok(s), false, false, false) } } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as -/// `+00:00`, and allows missing minutes entirely. -pub fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - match s.as_bytes().first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true), +/// Tries to consume an RFC2822 comment including preceding ` `. +/// +/// Returns the remaining string after the closing parenthesis. +pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> { + use CommentState::*; + + let s = s.trim_start(); + + let mut state = Start; + for (i, c) in s.bytes().enumerate() { + state = match (state, c) { + (Start, b'(') => Next(1), + (Next(1), b')') => return Ok((&s[i + 1..], ())), + (Next(depth), b'\\') => Escape(depth), + (Next(depth), b'(') => Next(depth + 1), + (Next(depth), b')') => Next(depth - 1), + (Next(depth), _) | (Escape(depth), _) => Next(depth), + _ => return Err(INVALID), + }; } + + Err(TOO_SHORT) } -/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. -/// May return `None` which indicates an insufficient offset data (i.e. `-0000`). -pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { - // tries to parse legacy time zone names - let upto = s - .as_bytes() - .iter() - .position(|&c| match c { - b'a'...b'z' | b'A'...b'Z' => false, - _ => true, - }) - .unwrap_or(s.len()); - if upto > 0 { - let name = &s[..upto]; - let s = &s[upto..]; - let offset_hours = |o| Ok((s, Some(o * 3600))); - if equals(name, "gmt") || equals(name, "ut") { - offset_hours(0) - } else if equals(name, "edt") { - offset_hours(-4) - } else if equals(name, "est") || equals(name, "cdt") { - offset_hours(-5) - } else if equals(name, "cst") || equals(name, "mdt") { - offset_hours(-6) - } else if equals(name, "mst") || equals(name, "pdt") { - offset_hours(-7) - } else if equals(name, "pst") { - offset_hours(-8) - } else { - Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000 +enum CommentState { + Start, + Next(usize), + Escape(usize), +} + +#[cfg(test)] +mod tests { + use super::{ + comment_2822, nanosecond, nanosecond_fixed, short_or_long_month0, short_or_long_weekday, + timezone_offset_2822, + }; + use crate::Weekday; + use crate::format::{INVALID, TOO_SHORT}; + + #[test] + fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + (" ", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + (" \r\n\t()", Ok("")), + ("() ", Ok(" ")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata.iter() { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!( + *expected, actual, + "{test_in:?} expected to produce {expected:?}, but produced {actual:?}." + ); } - } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s))?; - Ok((s_, Some(offset))) } -} -/// Tries to consume everyting until next whitespace-like symbol. -/// Does not provide any offset information from the consumed data. -pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) + #[test] + fn test_timezone_offset_2822() { + assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", -21600)); + assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", -28800)); + assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", -25200)); + assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", -57060)); + assert_eq!(timezone_offset_2822("Gp"), Err(INVALID)); + } + + #[test] + fn test_short_or_long_month0() { + assert_eq!(short_or_long_month0("JUn").unwrap(), ("", 5)); + assert_eq!(short_or_long_month0("mAy").unwrap(), ("", 4)); + assert_eq!(short_or_long_month0("AuG").unwrap(), ("", 7)); + assert_eq!(short_or_long_month0("Aprâ").unwrap(), ("â", 3)); + assert_eq!(short_or_long_month0("JUl").unwrap(), ("", 6)); + assert_eq!(short_or_long_month0("mAr").unwrap(), ("", 2)); + assert_eq!(short_or_long_month0("Jan").unwrap(), ("", 0)); + } + + #[test] + fn test_short_or_long_weekday() { + assert_eq!(short_or_long_weekday("sAtu").unwrap(), ("u", Weekday::Sat)); + assert_eq!(short_or_long_weekday("thu").unwrap(), ("", Weekday::Thu)); + } + + #[test] + fn test_nanosecond_fixed() { + assert_eq!(nanosecond_fixed("", 0usize).unwrap(), ("", 0)); + assert!(nanosecond_fixed("", 1usize).is_err()); + } + + #[test] + fn test_nanosecond() { + assert_eq!(nanosecond("2Ù").unwrap(), ("Ù", 200000000)); + assert_eq!(nanosecond("8").unwrap(), ("", 800000000)); + } } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index c6e3e721fc..d93ef814c8 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -11,10 +11,11 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] | -| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] | -| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).| +| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | +| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | +| `%q` | `1` | Quarter of year (1-4) | | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. | @@ -28,12 +29,12 @@ The following specifiers are available both to formatting and parsing. | `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. | | `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) | | | | | -| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] | +| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2] | | `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.| | | | | -| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] | -| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] | -| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] | +| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^3] | +| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^3] | +| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] | | | | | | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. | | | | | @@ -52,32 +53,34 @@ The following specifiers are available both to formatting and parsing. | `%p` | `AM` | `AM` or `PM` in 12-hour clocks. | | | | | | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | -| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] | -| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] | -| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] | -| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] | -| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] | -| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] | -| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] | -| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] | -| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] | +| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | +| `%f` | `26490000` | Number of nanoseconds since last whole second. [^7] | +| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7] | +| `%.3f`| `.026` | Decimal fraction of a second with a fixed length of 3. | +| `%.6f`| `.026490` | Decimal fraction of a second with a fixed length of 6. | +| `%.9f`| `.026490000` | Decimal fraction of a second with a fixed length of 9. | +| `%3f` | `026` | Decimal fraction of a second like `%.3f` but without the leading dot. | +| `%6f` | `026490` | Decimal fraction of a second like `%.6f` but without the leading dot. | +| `%9f` | `026490000` | Decimal fraction of a second like `%.9f` but without the leading dot. | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). | -| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | +| `%r` | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. | | | | | | | | **TIME ZONE SPECIFIERS:** | -| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] | +| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] | | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | | `%:z` | `+09:30` | Same as `%z` but with a colon. | +|`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds. | +|`%:::z`| `+09` | Offset from the local time to UTC without minutes. | | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. | | | | | | | | **DATE & TIME SPECIFIERS:** | |`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). | -| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] | +| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5] | | | | | -| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]| +| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]| | | | | | | | **SPECIAL SPECIFIERS:** | | `%t` | | Literal tab (`\t`). | @@ -95,69 +98,57 @@ Modifier | Description Notes: -[^1]: `%Y`: - Negative years are allowed in formatting but not in parsing. - -[^2]: `%C`, `%y`: +[^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. + For `%y`, values greater or equal to 70 are interpreted as being in the 20th century, + values smaller than 70 in the 21st century. -[^3]: `%U`: +[^2]: `%U`: Week 1 starts with the first Sunday in that year. It is possible to have week 0 for days before the first Sunday. -[^4]: `%G`, `%g`, `%V`: +[^3]: `%G`, `%g`, `%V`: Week 1 is the first week with at least 4 days in that year. Week 0 does not exist, so this should be used with `%G` or `%g`. -[^5]: `%S`: +[^4]: `%S`: It accounts for leap seconds, so `60` is possible. -[^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional +[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional digits for seconds and colons in the time zone offset.

+ This format also supports having a `Z` or `UTC` in place of `%:z`. They + are equivalent to `+00:00`. +
+
+ Note that all `T`, `Z`, and `UTC` are parsed case-insensitively. +
+
The typical `strftime` implementations have different (and locale-dependent) formats for this specifier. While Chrono's format for `%+` is far more stable, it is best to avoid this specifier if you want to control the exact output. -[^7]: `%s`: +[^6]: `%s`: This is not padded and can be negative. For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -[^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: +[^7]: `%f`, `%.f`:
- The default `%f` is right-aligned and always zero-padded to 9 digits - for the compatibility with glibc and others, - so it always counts the number of nanoseconds since the last whole second. - E.g. 7ms after the last second will print `007000000`, - and parsing `7000000` will yield the same. -
-
- The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits - according to the precision. - E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can print or read nothing if the fractional part is zero or - the next character is not `.`. -
-
- The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`. - E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero or - the next character is not `.` however will print with the specified length. + `%f` and `%.f` are notably different formatting specifiers.
+ `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a + second.
+ Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`. + +[^8]: `%Z`: + Since `chrono` is not aware of timezones beyond their offsets, this specifier + **only prints the offset** when used for formatting. The timezone abbreviation + will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960) + for more information.

- The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`, but without the leading dot. - E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), - and parsing `07`, `070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero. - -[^9]: `%Z`: Offset will not be populated from the parsed data, nor will it be validated. Timezone is completely ignored. Similar to the glibc `strptime` treatment of this format code. @@ -168,482 +159,1059 @@ Notes: China Daylight Time. */ -#[cfg(feature = "unstable-locales")] -use super::{locales, Locale}; -use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(any(feature = "alloc", feature = "std"))] +use super::{BAD_FORMAT, ParseError}; +use super::{Fixed, InternalInternal, Item, Numeric, Pad}; #[cfg(feature = "unstable-locales")] -type Fmt<'a> = Vec>; -#[cfg(not(feature = "unstable-locales"))] -type Fmt<'a> = &'static [Item<'static>]; - -static D_FMT: &'static [Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &'static [Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), -]; -static T_FMT: &'static [Item<'static>] = - &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; +use super::{Locale, locales}; +use super::{fixed, internal_fixed, num, num0, nums}; +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::vec::Vec; /// Parsing iterator for `strftime`-like format strings. +/// +/// See the [`format::strftime` module](crate::format::strftime) for supported formatting +/// specifiers. +/// +/// `StrftimeItems` is used in combination with more low-level methods such as [`format::parse()`] +/// or [`format_with_items`]. +/// +/// If formatting or parsing date and time values is not performance-critical, the methods +/// [`parse_from_str`] and [`format`] on types such as [`DateTime`](crate::DateTime) are easier to +/// use. +/// +/// [`format`]: crate::DateTime::format +/// [`format_with_items`]: crate::DateTime::format +/// [`parse_from_str`]: crate::DateTime::parse_from_str +/// [`DateTime`]: crate::DateTime +/// [`format::parse()`]: crate::format::parse() #[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct StrftimeItems<'a> { /// Remaining portion of the string. remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), - /// parser refers to the statically reconstructed slice of them. - /// If `recons` is not empty they have to be returned earlier than the `remainder`. - recons: Fmt<'a>, - /// Date format - d_fmt: Fmt<'a>, - /// Date and time format - d_t_fmt: Fmt<'a>, - /// Time format - t_fmt: Fmt<'a>, + /// `queue` stores a slice of `Item`s that have to be returned one by one. + queue: &'static [Item<'static>], + lenient: bool, + #[cfg(feature = "unstable-locales")] + locale_str: &'a str, + #[cfg(feature = "unstable-locales")] + locale: Option, } impl<'a> StrftimeItems<'a> { - /// Creates a new parsing iterator from the `strftime`-like format string. - pub fn new(s: &'a str) -> StrftimeItems<'a> { - Self::with_remainer(s) - } - - /// Creates a new parsing iterator from the `strftime`-like format string. - #[cfg(feature = "unstable-locales")] - pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { - let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); - let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); - let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - + /// Creates a new parsing iterator from a `strftime`-like format string. + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::*; + /// + /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601) + /// + /// const ISO8601_YMD_ITEMS: &[Item<'static>] = &[ + /// Item::Numeric(Numeric::Year, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Month, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Day, Pad::Zero), + /// ]; + /// assert!(strftime_parser.eq(ISO8601_YMD_ITEMS.iter().cloned())); + /// ``` + #[must_use] + pub const fn new(s: &'a str) -> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: Vec::new(), - d_fmt: d_fmt, - d_t_fmt: d_t_fmt, - t_fmt: t_fmt, + queue: &[], + lenient: false, + #[cfg(feature = "unstable-locales")] + locale_str: "", + #[cfg(feature = "unstable-locales")] + locale: None, } } - #[cfg(not(feature = "unstable-locales"))] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &'static [Item<'static>; 0] = &[]; - + /// The same as [`StrftimeItems::new`], but returns [`Item::Literal`] instead of [`Item::Error`]. + /// + /// Useful for formatting according to potentially invalid format strings. + /// + /// # Example + /// + /// ``` + /// use chrono::format::*; + /// + /// let strftime_parser = StrftimeItems::new_lenient("%Y-%Q"); // %Y: year, %Q: invalid + /// + /// const ITEMS: &[Item<'static>] = &[ + /// Item::Numeric(Numeric::Year, Pad::Zero), + /// Item::Literal("-"), + /// Item::Literal("%Q"), + /// ]; + /// println!("{:?}", strftime_parser.clone().collect::>()); + /// assert!(strftime_parser.eq(ITEMS.iter().cloned())); + /// ``` + #[must_use] + pub const fn new_lenient(s: &'a str) -> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: FMT_NONE, - d_fmt: D_FMT, - d_t_fmt: D_T_FMT, - t_fmt: T_FMT, + queue: &[], + lenient: true, + #[cfg(feature = "unstable-locales")] + locale_str: "", + #[cfg(feature = "unstable-locales")] + locale: None, } } + /// Creates a new parsing iterator from a `strftime`-like format string, with some formatting + /// specifiers adjusted to match [`Locale`]. + /// + /// Note: `StrftimeItems::new_with_locale` only localizes the *format*. You usually want to + /// combine it with other locale-aware methods such as + /// [`DateTime::format_localized_with_items`] to get things like localized month or day names. + /// + /// The `%x` formatting specifier will use the local date format, `%X` the local time format, + /// and `%c` the local format for date and time. + /// `%r` will use the local 12-hour clock format (e.g., 11:11:04 PM). Not all locales have such + /// a format, in which case we fall back to a 24-hour clock (`%X`). + /// + /// See the [`format::strftime` module](crate::format::strftime) for all supported formatting + /// specifiers. + /// + /// [`DateTime::format_localized_with_items`]: crate::DateTime::format_localized_with_items + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::format::{Locale, StrftimeItems}; + /// use chrono::{FixedOffset, TimeZone}; + /// + /// let dt = FixedOffset::east_opt(9 * 60 * 60) + /// .unwrap() + /// .with_ymd_and_hms(2023, 7, 11, 0, 34, 59) + /// .unwrap(); + /// + /// // Note: you usually want to combine `StrftimeItems::new_with_locale` with other + /// // locale-aware methods such as `DateTime::format_localized_with_items`. + /// // We use the regular `format_with_items` to show only how the formatting changes. + /// + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::en_US)); + /// assert_eq!(fmtr.to_string(), "07/11/2023"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ko_KR)); + /// assert_eq!(fmtr.to_string(), "2023년 07월 11일"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP)); + /// assert_eq!(fmtr.to_string(), "2023年07月11日"); + /// # } + /// ``` #[cfg(feature = "unstable-locales")] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { + #[must_use] + pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: Vec::new(), - d_fmt: D_FMT.to_vec(), - d_t_fmt: D_T_FMT.to_vec(), - t_fmt: T_FMT.to_vec(), + queue: &[], + lenient: false, + locale_str: "", + locale: Some(locale), } } -} -const HAVE_ALTERNATES: &'static str = "z"; + /// Parse format string into a `Vec` of formatting [`Item`]'s. + /// + /// If you need to format or parse multiple values with the same format string, it is more + /// efficient to convert it to a `Vec` of formatting [`Item`]'s than to re-parse the format + /// string on every use. + /// + /// The `format_with_items` methods on [`DateTime`], [`NaiveDateTime`], [`NaiveDate`] and + /// [`NaiveTime`] accept the result for formatting. [`format::parse()`] can make use of it for + /// parsing. + /// + /// [`DateTime`]: crate::DateTime::format_with_items + /// [`NaiveDateTime`]: crate::NaiveDateTime::format_with_items + /// [`NaiveDate`]: crate::NaiveDate::format_with_items + /// [`NaiveTime`]: crate::NaiveTime::format_with_items + /// [`format::parse()`]: crate::format::parse() + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient]. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{parse, Parsed, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// let fmt_items = StrftimeItems::new("%e %b %Y %k.%M").parse()?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// // Formatting + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// + /// // Parsing + /// let mut parsed = Parsed::new(); + /// parse(&mut parsed, "11 Jul 2023 9.00", fmt_items.as_slice().iter())?; + /// let parsed_dt = parsed.to_naive_datetime_with_offset(0)?; + /// assert_eq!(parsed_dt, datetime); + /// # Ok::<(), chrono::ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item), + true => Err(BAD_FORMAT), + }) + .collect() + } -impl<'a> Iterator for StrftimeItems<'a> { - type Item = Item<'a>; + /// Parse format string into a `Vec` of [`Item`]'s that contain no references to slices of the + /// format string. + /// + /// A `Vec` created with [`StrftimeItems::parse`] contains references to the format string, + /// binding the lifetime of the `Vec` to that string. [`StrftimeItems::parse_to_owned`] will + /// convert the references to owned types. + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient]. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{Item, ParseError, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result>, ParseError> { + /// // `fmt_string` is dropped at the end of this function. + /// let fmt_string = format!("{} {}", date_fmt, time_fmt); + /// StrftimeItems::new(&fmt_string).parse_to_owned() + /// } + /// + /// let fmt_items = format_items("%e %b %Y", "%k.%M")?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// # Ok::<(), ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse_to_owned(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item.to_owned()), + true => Err(BAD_FORMAT), + }) + .collect() + } - fn next(&mut self) -> Option> { - // we have some reconstructed items to return - if !self.recons.is_empty() { - let item; - #[cfg(feature = "unstable-locales")] - { - item = self.recons.remove(0); + fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + use InternalInternal::*; + use Item::{Literal, Space}; + use Numeric::*; + + let (original, mut remainder) = match remainder.chars().next()? { + // the next item is a specifier + '%' => (remainder, &remainder[1..]), + + // the next item is space + c if c.is_whitespace() => { + // `%` is not a whitespace, so `c != '%'` is redundant + let nextspec = + remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); + assert!(nextspec > 0); + let item = Space(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + return Some((remainder, item)); } - #[cfg(not(feature = "unstable-locales"))] - { - item = self.recons[0].clone(); - self.recons = &self.recons[1..]; + + // the next item is literal + _ => { + let nextspec = remainder + .find(|c: char| c.is_whitespace() || c == '%') + .unwrap_or(remainder.len()); + assert!(nextspec > 0); + let item = Literal(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + return Some((remainder, item)); } - return Some(item); + }; + + macro_rules! next { + () => { + match remainder.chars().next() { + Some(x) => { + remainder = &remainder[x.len_utf8()..]; + x + } + None => return Some((remainder, self.error(original, remainder))), // premature end of string + } + }; } - match self.remainder.chars().next() { - // we are done - None => None, + let spec = next!(); + let pad_override = match spec { + '-' => Some(Pad::None), + '0' => Some(Pad::Zero), + '_' => Some(Pad::Space), + _ => None, + }; - // the next item is a specifier - Some('%') => { - self.remainder = &self.remainder[1..]; - - macro_rules! next { - () => { - match self.remainder.chars().next() { - Some(x) => { - self.remainder = &self.remainder[x.len_utf8()..]; - x - } - None => return Some(Item::Error), // premature end of string - } - }; - } + let is_alternate = spec == '#'; + let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; + if is_alternate && !HAVE_ALTERNATES.contains(spec) { + return Some((remainder, self.error(original, remainder))); + } - let spec = next!(); - let pad_override = match spec { - '-' => Some(Pad::None), - '0' => Some(Pad::Zero), - '_' => Some(Pad::Space), - _ => None, - }; - let is_alternate = spec == '#'; - let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; - if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(Item::Error); - } + macro_rules! queue { + [$head:expr, $($tail:expr),+ $(,)*] => ({ + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; + $head + }) + } - macro_rules! recons { - [$head:expr, $($tail:expr),+ $(,)*] => ({ - #[cfg(feature = "unstable-locales")] - { - self.recons.clear(); - $(self.recons.push($tail);)+ - } - #[cfg(not(feature = "unstable-locales"))] - { - const RECONS: &'static [Item<'static>] = &[$($tail),+]; - self.recons = RECONS; - } - $head - }) - } + #[cfg(not(feature = "unstable-locales"))] + macro_rules! queue_from_slice { + ($slice:expr) => {{ + self.queue = &$slice[1..]; + $slice[0].clone() + }}; + } - macro_rules! recons_from_slice { - ($slice:expr) => {{ - #[cfg(feature = "unstable-locales")] - { - self.recons.clear(); - self.recons.extend_from_slice(&$slice[1..]); - } - #[cfg(not(feature = "unstable-locales"))] - { - self.recons = &$slice[1..]; - } - $slice[0].clone() - }}; + let item = match spec { + 'A' => fixed(Fixed::LongWeekdayName), + 'B' => fixed(Fixed::LongMonthName), + 'C' => num0(YearDiv100), + 'D' => { + queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] + } + 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], + 'G' => num0(IsoYear), + 'H' => num0(Hour), + 'I' => num0(Hour12), + 'M' => num0(Minute), + 'P' => fixed(Fixed::LowerAmPm), + 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], + 'S' => num0(Second), + 'T' => { + queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] + } + 'U' => num0(WeekFromSun), + 'V' => num0(IsoWeek), + 'W' => num0(WeekFromMon), + #[cfg(not(feature = "unstable-locales"))] + 'X' => queue_from_slice!(T_FMT), + #[cfg(feature = "unstable-locales")] + 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), + 'Y' => num0(Year), + 'Z' => fixed(Fixed::TimezoneName), + 'a' => fixed(Fixed::ShortWeekdayName), + 'b' | 'h' => fixed(Fixed::ShortMonthName), + #[cfg(not(feature = "unstable-locales"))] + 'c' => queue_from_slice!(D_T_FMT), + #[cfg(feature = "unstable-locales")] + 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), + 'd' => num0(Day), + 'e' => nums(Day), + 'f' => num0(Nanosecond), + 'g' => num0(IsoYearMod100), + 'j' => num0(Ordinal), + 'k' => nums(Hour), + 'l' => nums(Hour12), + 'm' => num0(Month), + 'n' => Space("\n"), + 'p' => fixed(Fixed::UpperAmPm), + 'q' => num(Quarter), + #[cfg(not(feature = "unstable-locales"))] + 'r' => queue_from_slice!(T_FMT_AMPM), + #[cfg(feature = "unstable-locales")] + 'r' => { + if self.locale.is_some() && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() { + // 12-hour clock not supported by this locale. Switch to 24-hour format. + self.switch_to_locale_str(locales::t_fmt, T_FMT) + } else { + self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) } - - let item = match spec { - 'A' => fix!(LongWeekdayName), - 'B' => fix!(LongMonthName), - 'C' => num0!(YearDiv100), - 'D' => { - recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] - } - 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], - 'G' => num0!(IsoYear), - 'H' => num0!(Hour), - 'I' => num0!(Hour12), - 'M' => num0!(Minute), - 'P' => fix!(LowerAmPm), - 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)], - 'S' => num0!(Second), - 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], - 'U' => num0!(WeekFromSun), - 'V' => num0!(IsoWeek), - 'W' => num0!(WeekFromMon), - 'X' => recons_from_slice!(self.t_fmt), - 'Y' => num0!(Year), - 'Z' => fix!(TimezoneName), - 'a' => fix!(ShortWeekdayName), - 'b' | 'h' => fix!(ShortMonthName), - 'c' => recons_from_slice!(self.d_t_fmt), - 'd' => num0!(Day), - 'e' => nums!(Day), - 'f' => num0!(Nanosecond), - 'g' => num0!(IsoYearMod100), - 'j' => num0!(Ordinal), - 'k' => nums!(Hour), - 'l' => nums!(Hour12), - 'm' => num0!(Month), - 'n' => sp!("\n"), - 'p' => fix!(UpperAmPm), - 'r' => recons![ - num0!(Hour12), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - fix!(UpperAmPm) - ], - 's' => num!(Timestamp), - 't' => sp!("\t"), - 'u' => num!(WeekdayFromMon), - 'v' => { - recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] - } - 'w' => num!(NumDaysFromSun), - 'x' => recons_from_slice!(self.d_fmt), - 'y' => num0!(YearMod100), - 'z' => { - if is_alternate { - internal_fix!(TimezoneOffsetPermissive) - } else { - fix!(TimezoneOffset) - } - } - '+' => fix!(RFC3339), - ':' => match next!() { - 'z' => fix!(TimezoneOffsetColon), - _ => Item::Error, - }, - '.' => match next!() { - '3' => match next!() { - 'f' => fix!(Nanosecond3), - _ => Item::Error, - }, - '6' => match next!() { - 'f' => fix!(Nanosecond6), - _ => Item::Error, - }, - '9' => match next!() { - 'f' => fix!(Nanosecond9), - _ => Item::Error, - }, - 'f' => fix!(Nanosecond), - _ => Item::Error, - }, - '3' => match next!() { - 'f' => internal_fix!(Nanosecond3NoDot), - _ => Item::Error, - }, - '6' => match next!() { - 'f' => internal_fix!(Nanosecond6NoDot), - _ => Item::Error, - }, - '9' => match next!() { - 'f' => internal_fix!(Nanosecond9NoDot), - _ => Item::Error, - }, - '%' => lit!("%"), - _ => Item::Error, // no such specifier - }; - - // adjust `item` if we have any padding modifier - if let Some(new_pad) = pad_override { - match item { - Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { - Some(Item::Numeric(kind.clone(), new_pad)) - } - _ => Some(Item::Error), // no reconstructed or non-numeric item allowed - } + } + 's' => num(Timestamp), + 't' => Space("\t"), + 'u' => num(WeekdayFromMon), + 'v' => { + queue![ + nums(Day), + Literal("-"), + fixed(Fixed::ShortMonthName), + Literal("-"), + num0(Year) + ] + } + 'w' => num(NumDaysFromSun), + #[cfg(not(feature = "unstable-locales"))] + 'x' => queue_from_slice!(D_FMT), + #[cfg(feature = "unstable-locales")] + 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), + 'y' => num0(YearMod100), + 'z' => { + if is_alternate { + internal_fixed(TimezoneOffsetPermissive) } else { - Some(item) + fixed(Fixed::TimezoneOffset) } } - - // the next item is space - Some(c) if c.is_whitespace() => { - // `%` is not a whitespace, so `c != '%'` is redundant - let nextspec = self - .remainder - .find(|c: char| !c.is_whitespace()) - .unwrap_or(self.remainder.len()); - assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + '+' => fixed(Fixed::RFC3339), + ':' => { + if remainder.starts_with("::z") { + remainder = &remainder[3..]; + fixed(Fixed::TimezoneOffsetTripleColon) + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; + fixed(Fixed::TimezoneOffsetDoubleColon) + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; + fixed(Fixed::TimezoneOffsetColon) + } else { + self.error(original, remainder) + } } + '.' => match next!() { + '3' => match next!() { + 'f' => fixed(Fixed::Nanosecond3), + _ => self.error(original, remainder), + }, + '6' => match next!() { + 'f' => fixed(Fixed::Nanosecond6), + _ => self.error(original, remainder), + }, + '9' => match next!() { + 'f' => fixed(Fixed::Nanosecond9), + _ => self.error(original, remainder), + }, + 'f' => fixed(Fixed::Nanosecond), + _ => self.error(original, remainder), + }, + '3' => match next!() { + 'f' => internal_fixed(Nanosecond3NoDot), + _ => self.error(original, remainder), + }, + '6' => match next!() { + 'f' => internal_fixed(Nanosecond6NoDot), + _ => self.error(original, remainder), + }, + '9' => match next!() { + 'f' => internal_fixed(Nanosecond9NoDot), + _ => self.error(original, remainder), + }, + '%' => Literal("%"), + _ => self.error(original, remainder), + }; - // the next item is literal - _ => { - let nextspec = self - .remainder - .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or(self.remainder.len()); - assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + // Adjust `item` if we have any padding modifier. + // Not allowed on non-numeric items or on specifiers composed out of multiple + // formatting items. + if let Some(new_pad) = pad_override { + match item { + Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { + Some((remainder, Item::Numeric(kind.clone(), new_pad))) + } + _ => Some((remainder, self.error(original, remainder))), } + } else { + Some((remainder, item)) } } -} -#[cfg(test)] -#[test] -fn test_strftime_items() { - fn parse_and_collect(s: &str) -> Vec> { - // map any error into `[Item::Error]`. useful for easy testing. - let items = StrftimeItems::new(s); - let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); - items.collect::>>().unwrap_or_else(|| vec![Item::Error]) + fn error<'b>(&mut self, original: &'b str, remainder: &'b str) -> Item<'b> { + match self.lenient { + false => Item::Error, + true => Item::Literal(&original[..original.len() - remainder.len()]), + } } - assert_eq!(parse_and_collect(""), []); - assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); - assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); - assert_eq!( - parse_and_collect("a b\t\nc"), - [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] - ); - assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); - assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); - assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); - assert_eq!( - parse_and_collect("%Y-%m-%d"), - [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] - ); - assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); - assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); - assert_eq!(parse_and_collect("%"), [Item::Error]); - assert_eq!(parse_and_collect("%%"), [lit!("%")]); - assert_eq!(parse_and_collect("%%%"), [Item::Error]); - assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); - assert_eq!(parse_and_collect("foo%?"), [Item::Error]); - assert_eq!(parse_and_collect("bar%42"), [Item::Error]); - assert_eq!(parse_and_collect("quux% +"), [Item::Error]); - assert_eq!(parse_and_collect("%.Z"), [Item::Error]); - assert_eq!(parse_and_collect("%:Z"), [Item::Error]); - assert_eq!(parse_and_collect("%-Z"), [Item::Error]); - assert_eq!(parse_and_collect("%0Z"), [Item::Error]); - assert_eq!(parse_and_collect("%_Z"), [Item::Error]); - assert_eq!(parse_and_collect("%.j"), [Item::Error]); - assert_eq!(parse_and_collect("%:j"), [Item::Error]); - assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); - assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); - assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); - assert_eq!(parse_and_collect("%.e"), [Item::Error]); - assert_eq!(parse_and_collect("%:e"), [Item::Error]); - assert_eq!(parse_and_collect("%-e"), [num!(Day)]); - assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); - assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); - assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); - assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); - assert_eq!(parse_and_collect("%#m"), [Item::Error]); + #[cfg(feature = "unstable-locales")] + fn switch_to_locale_str( + &mut self, + localized_fmt_str: impl Fn(Locale) -> &'static str, + fallback: &'static [Item<'static>], + ) -> Item<'a> { + if let Some(locale) = self.locale { + assert!(self.locale_str.is_empty()); + let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap(); + self.locale_str = fmt_str; + item + } else { + self.queue = &fallback[1..]; + fallback[0].clone() + } + } } -#[cfg(test)] -#[test] -fn test_strftime_docs() { - use {FixedOffset, TimeZone, Timelike}; - - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); - - // date specifiers - assert_eq!(dt.format("%Y").to_string(), "2001"); - assert_eq!(dt.format("%C").to_string(), "20"); - assert_eq!(dt.format("%y").to_string(), "01"); - assert_eq!(dt.format("%m").to_string(), "07"); - assert_eq!(dt.format("%b").to_string(), "Jul"); - assert_eq!(dt.format("%B").to_string(), "July"); - assert_eq!(dt.format("%h").to_string(), "Jul"); - assert_eq!(dt.format("%d").to_string(), "08"); - assert_eq!(dt.format("%e").to_string(), " 8"); - assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); - assert_eq!(dt.format("%a").to_string(), "Sun"); - assert_eq!(dt.format("%A").to_string(), "Sunday"); - assert_eq!(dt.format("%w").to_string(), "0"); - assert_eq!(dt.format("%u").to_string(), "7"); - assert_eq!(dt.format("%U").to_string(), "28"); - assert_eq!(dt.format("%W").to_string(), "27"); - assert_eq!(dt.format("%G").to_string(), "2001"); - assert_eq!(dt.format("%g").to_string(), "01"); - assert_eq!(dt.format("%V").to_string(), "27"); - assert_eq!(dt.format("%j").to_string(), "189"); - assert_eq!(dt.format("%D").to_string(), "07/08/01"); - assert_eq!(dt.format("%x").to_string(), "07/08/01"); - assert_eq!(dt.format("%F").to_string(), "2001-07-08"); - assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); - - // time specifiers - assert_eq!(dt.format("%H").to_string(), "00"); - assert_eq!(dt.format("%k").to_string(), " 0"); - assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); - assert_eq!(dt.format("%I").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); - assert_eq!(dt.format("%P").to_string(), "am"); - assert_eq!(dt.format("%p").to_string(), "AM"); - assert_eq!(dt.format("%M").to_string(), "34"); - assert_eq!(dt.format("%S").to_string(), "60"); - assert_eq!(dt.format("%f").to_string(), "026490708"); - assert_eq!(dt.format("%.f").to_string(), ".026490708"); - assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); - assert_eq!(dt.format("%.3f").to_string(), ".026"); - assert_eq!(dt.format("%.6f").to_string(), ".026490"); - assert_eq!(dt.format("%.9f").to_string(), ".026490708"); - assert_eq!(dt.format("%3f").to_string(), "026"); - assert_eq!(dt.format("%6f").to_string(), "026490"); - assert_eq!(dt.format("%9f").to_string(), "026490708"); - assert_eq!(dt.format("%R").to_string(), "00:34"); - assert_eq!(dt.format("%T").to_string(), "00:34:60"); - assert_eq!(dt.format("%X").to_string(), "00:34:60"); - assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); - - // time zone specifiers - //assert_eq!(dt.format("%Z").to_string(), "ACST"); - assert_eq!(dt.format("%z").to_string(), "+0930"); - assert_eq!(dt.format("%:z").to_string(), "+09:30"); - - // date & time specifiers - assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); - assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); - assert_eq!( - dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), - "2001-07-08T00:34:60.026490+09:30" - ); - assert_eq!(dt.format("%s").to_string(), "994518299"); - - // special specifiers - assert_eq!(dt.format("%t").to_string(), "\t"); - assert_eq!(dt.format("%n").to_string(), "\n"); - assert_eq!(dt.format("%%").to_string(), "%"); +impl<'a> Iterator for StrftimeItems<'a> { + type Item = Item<'a>; + + fn next(&mut self) -> Option> { + // We have items queued to return from a specifier composed of multiple formatting items. + if let Some((item, remainder)) = self.queue.split_first() { + self.queue = remainder; + return Some(item.clone()); + } + + // We are in the middle of parsing the localized formatting string of a specifier. + #[cfg(feature = "unstable-locales")] + if !self.locale_str.is_empty() { + let (remainder, item) = self.parse_next_item(self.locale_str)?; + self.locale_str = remainder; + return Some(item); + } + + // Normal: we are parsing the formatting string. + let (remainder, item) = self.parse_next_item(self.remainder)?; + self.remainder = remainder; + Some(item) + } } -#[cfg(feature = "unstable-locales")] -#[test] -fn test_strftime_docs_localized() { - use {FixedOffset, TimeZone}; - - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); - - // date specifiers - assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); - assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); - assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); - assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); - assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); - assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); - - // time specifiers - assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); - assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); - - // date & time specifiers - assert_eq!( - dt.format_localized("%c", Locale::fr_BE).to_string(), - "dim 08 jui 2001 00:34:60 +09:30" - ); +static D_FMT: &[Item<'static>] = &[ + num0(Numeric::Month), + Item::Literal("/"), + num0(Numeric::Day), + Item::Literal("/"), + num0(Numeric::YearMod100), +]; +static D_T_FMT: &[Item<'static>] = &[ + fixed(Fixed::ShortWeekdayName), + Item::Space(" "), + fixed(Fixed::ShortMonthName), + Item::Space(" "), + nums(Numeric::Day), + Item::Space(" "), + num0(Numeric::Hour), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), + Item::Space(" "), + num0(Numeric::Year), +]; +static T_FMT: &[Item<'static>] = &[ + num0(Numeric::Hour), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), +]; +static T_FMT_AMPM: &[Item<'static>] = &[ + num0(Numeric::Hour12), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), + Item::Space(" "), + fixed(Fixed::UpperAmPm), +]; + +const HAVE_ALTERNATES: &str = "z"; + +#[cfg(test)] +mod tests { + use super::StrftimeItems; + use crate::format::Item::{self, Literal, Space}; + #[cfg(feature = "unstable-locales")] + use crate::format::Locale; + use crate::format::{Fixed, InternalInternal, Numeric::*}; + use crate::format::{fixed, internal_fixed, num, num0, nums}; + #[cfg(feature = "alloc")] + use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; + + #[test] + fn test_strftime_items() { + fn parse_and_collect(s: &str) -> Vec> { + // map any error into `[Item::Error]`. useful for easy testing. + eprintln!("test_strftime_items: parse_and_collect({s:?})"); + let items = StrftimeItems::new(s); + let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); + items.collect::>>().unwrap_or_else(|| vec![Item::Error]) + } + + assert_eq!(parse_and_collect(""), []); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect("a"), [Literal("a")]); + assert_eq!(parse_and_collect("ab"), [Literal("ab")]); + assert_eq!(parse_and_collect("😽"), [Literal("😽")]); + assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]); + assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]); + assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); + assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); + assert_eq!( + parse_and_collect("a b\t\nc"), + [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")] + ); + assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]); + assert_eq!( + parse_and_collect("100%% ok"), + [Literal("100"), Literal("%"), Space(" "), Literal("ok")] + ); + assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]); + assert_eq!( + parse_and_collect("%Y-%m-%d"), + [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] + ); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]); + assert_eq!( + parse_and_collect("😽😽a b😽c"), + [Literal("😽😽a"), Space(" "), Literal("b😽c")] + ); + assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽"), + [Space(" "), Literal("😽"), Space(" "), Literal("😽")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽はい😽 ハンバーガー"), + [ + Space(" "), + Literal("😽"), + Space(" "), + Literal("😽はい😽"), + Space(" "), + Literal("ハンバーガー") + ] + ); + assert_eq!( + parse_and_collect("%%😽%%😽"), + [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")] + ); + assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]); + assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]); + assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); + assert_eq!(parse_and_collect("%"), [Item::Error]); + assert_eq!(parse_and_collect("%%"), [Literal("%")]); + assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]); + assert_eq!(parse_and_collect("%%a%"), [Item::Error]); + assert_eq!(parse_and_collect("%😽"), [Item::Error]); + assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); + assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); + assert_eq!( + parse_and_collect("%%%%ハンバーガー"), + [Literal("%"), Literal("%"), Literal("ハンバーガー")] + ); + assert_eq!(parse_and_collect("foo%?"), [Item::Error]); + assert_eq!(parse_and_collect("bar%42"), [Item::Error]); + assert_eq!(parse_and_collect("quux% +"), [Item::Error]); + assert_eq!(parse_and_collect("%.Z"), [Item::Error]); + assert_eq!(parse_and_collect("%:Z"), [Item::Error]); + assert_eq!(parse_and_collect("%-Z"), [Item::Error]); + assert_eq!(parse_and_collect("%0Z"), [Item::Error]); + assert_eq!(parse_and_collect("%_Z"), [Item::Error]); + assert_eq!(parse_and_collect("%.j"), [Item::Error]); + assert_eq!(parse_and_collect("%:j"), [Item::Error]); + assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]); + assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]); + assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]); + assert_eq!(parse_and_collect("%.e"), [Item::Error]); + assert_eq!(parse_and_collect("%:e"), [Item::Error]); + assert_eq!(parse_and_collect("%-e"), [num(Day)]); + assert_eq!(parse_and_collect("%0e"), [num0(Day)]); + assert_eq!(parse_and_collect("%_e"), [nums(Day)]); + assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]); + assert_eq!( + parse_and_collect("%#z"), + [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] + ); + assert_eq!(parse_and_collect("%#m"), [Item::Error]); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_strftime_docs() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); + + // date specifiers + assert_eq!(dt.format("%Y").to_string(), "2001"); + assert_eq!(dt.format("%C").to_string(), "20"); + assert_eq!(dt.format("%y").to_string(), "01"); + assert_eq!(dt.format("%q").to_string(), "3"); + assert_eq!(dt.format("%m").to_string(), "07"); + assert_eq!(dt.format("%b").to_string(), "Jul"); + assert_eq!(dt.format("%B").to_string(), "July"); + assert_eq!(dt.format("%h").to_string(), "Jul"); + assert_eq!(dt.format("%d").to_string(), "08"); + assert_eq!(dt.format("%e").to_string(), " 8"); + assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); + assert_eq!(dt.format("%a").to_string(), "Sun"); + assert_eq!(dt.format("%A").to_string(), "Sunday"); + assert_eq!(dt.format("%w").to_string(), "0"); + assert_eq!(dt.format("%u").to_string(), "7"); + assert_eq!(dt.format("%U").to_string(), "27"); + assert_eq!(dt.format("%W").to_string(), "27"); + assert_eq!(dt.format("%G").to_string(), "2001"); + assert_eq!(dt.format("%g").to_string(), "01"); + assert_eq!(dt.format("%V").to_string(), "27"); + assert_eq!(dt.format("%j").to_string(), "189"); + assert_eq!(dt.format("%D").to_string(), "07/08/01"); + assert_eq!(dt.format("%x").to_string(), "07/08/01"); + assert_eq!(dt.format("%F").to_string(), "2001-07-08"); + assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); + + // time specifiers + assert_eq!(dt.format("%H").to_string(), "00"); + assert_eq!(dt.format("%k").to_string(), " 0"); + assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); + assert_eq!(dt.format("%I").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); + assert_eq!(dt.format("%P").to_string(), "am"); + assert_eq!(dt.format("%p").to_string(), "AM"); + assert_eq!(dt.format("%M").to_string(), "34"); + assert_eq!(dt.format("%S").to_string(), "60"); + assert_eq!(dt.format("%f").to_string(), "026490708"); + assert_eq!(dt.format("%.f").to_string(), ".026490708"); + assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); + assert_eq!(dt.format("%.3f").to_string(), ".026"); + assert_eq!(dt.format("%.6f").to_string(), ".026490"); + assert_eq!(dt.format("%.9f").to_string(), ".026490708"); + assert_eq!(dt.format("%3f").to_string(), "026"); + assert_eq!(dt.format("%6f").to_string(), "026490"); + assert_eq!(dt.format("%9f").to_string(), "026490708"); + assert_eq!(dt.format("%R").to_string(), "00:34"); + assert_eq!(dt.format("%T").to_string(), "00:34:60"); + assert_eq!(dt.format("%X").to_string(), "00:34:60"); + assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); + + // time zone specifiers + //assert_eq!(dt.format("%Z").to_string(), "ACST"); + assert_eq!(dt.format("%z").to_string(), "+0930"); + assert_eq!(dt.format("%:z").to_string(), "+09:30"); + assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); + assert_eq!(dt.format("%:::z").to_string(), "+09"); + + // date & time specifiers + assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); + assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); + + assert_eq!( + dt.with_timezone(&Utc).format("%+").to_string(), + "2001-07-07T15:04:60.026490708+00:00" + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() + ); + + assert_eq!( + dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), + "2001-07-08T00:34:60.026490+09:30" + ); + assert_eq!(dt.format("%s").to_string(), "994518299"); + + // special specifiers + assert_eq!(dt.format("%t").to_string(), "\t"); + assert_eq!(dt.format("%n").to_string(), "\n"); + assert_eq!(dt.format("%%").to_string(), "%"); + + // complex format specifiers + assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); + assert_eq!( + dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), + " 20010807%%\t00:am:3460+09\t" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_docs_localized() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); + assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); + assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); + assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); + assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); + + // time specifiers + assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); + assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::fr_BE).to_string(), + "dim 08 jui 2001 00:34:60 +09:30" + ); + + let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); + + // date specifiers + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); + assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); + assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); + assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); + assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); + } + + /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does + /// not cause a panic. + /// + /// See . + #[test] + #[cfg(feature = "alloc")] + fn test_parse_only_timezone_offset_permissive_no_panic() { + use crate::NaiveDate; + use crate::{FixedOffset, TimeZone}; + use std::fmt::Write; + + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); + + let mut buf = String::new(); + let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_korean() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월"); + assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일"); + assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일"); + assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일"); + assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001"); + assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ko_KR).to_string(), + "2001년 07월 08일 (일) 오전 12시 34분 60초" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_japanese() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月"); + assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日"); + assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日"); + assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日"); + assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001"); + assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ja_JP).to_string(), + "2001年07月08日 00時34分60秒" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_time() { + let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap(); + // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+ + assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM"); + assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM"); + assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ"); + assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ"); + } + + #[test] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 56); + assert_eq!(size_of::(), 2); + } + + #[test] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 12); + assert_eq!(size_of::(), 28); + assert_eq!(size_of::(), 2); + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_strftime_parse() { + let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z"); + let fmt_items = fmt_str.parse().unwrap(); + let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); + assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000"); + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_strftime_parse_lenient() { + let fmt_str = StrftimeItems::new_lenient("%Y-%m-%dT%H:%M:%S%z%Q%.2f%%%"); + let fmt_items = fmt_str.parse().unwrap(); + let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); + assert_eq!( + &dt.format_with_items(fmt_items.iter()).to_string(), + "2014-05-07T12:34:56+0000%Q%.2f%%" + ); + } + + /// Regression test for https://github.com/chronotope/chrono/issues/1725 + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_finite() { + let mut i = 0; + for item in StrftimeItems::new("%2f") { + println!("{:?}", item); + i += 1; + if i > 10 { + panic!("infinite loop"); + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index e18a1da38e..0e942e02d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,186 +1,220 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - //! # Chrono: Date and Time for Rust //! -//! It aims to be a feature-complete superset of -//! the [time](https://github.com/rust-lang-deprecated/time) library. -//! In particular, -//! -//! * Chrono strictly adheres to ISO 8601. -//! * Chrono is timezone-aware by default, with separate timezone-naive types. -//! * Chrono is space-optimal and (while not being the primary goal) reasonably efficient. -//! -//! There were several previous attempts to bring a good date and time library to Rust, -//! which Chrono builds upon and should acknowledge: -//! -//! * [Initial research on -//! the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -//! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -//! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) -//! -//! Any significant changes to Chrono are documented in -//! the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. +//! Chrono aims to provide all functionality needed to do correct operations on dates and times in +//! the [proleptic Gregorian calendar]: //! -//! ## Usage +//! * The [`DateTime`] type is timezone-aware by default, with separate timezone-naive types. +//! * Operations that may produce an invalid or ambiguous date and time return `Option` or +//! [`MappedLocalTime`]. +//! * Configurable parsing and formatting with a `strftime` inspired date and time formatting +//! syntax. +//! * The [`Local`] timezone works with the current timezone of the OS. +//! * Types and operations are implemented to be reasonably efficient. //! -//! Put this in your `Cargo.toml`: +//! Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion +//! crate [Chrono-TZ] or [`tzfile`] for full timezone support. //! -//! ```toml -//! [dependencies] -//! chrono = "0.4" -//! ``` +//! [proleptic Gregorian calendar]: https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar +//! [Chrono-TZ]: https://crates.io/crates/chrono-tz +//! [`tzfile`]: https://crates.io/crates/tzfile //! //! ### Features //! -//! Chrono supports various runtime environments and operating systems, and has -//! several features that may be enabled or disabled. +//! Chrono supports various runtime environments and operating systems, and has several features +//! that may be enabled or disabled. //! //! Default features: //! -//! - `alloc`: Enable features that depend on allocation (primarily string formatting) -//! - `std`: Enables functionality that depends on the standard library. This -//! is a superset of `alloc` and adds interoperation with standard library types -//! and traits. -//! - `clock`: enables reading the system time (`now`), independent of whether -//! `std::time::SystemTime` is present, depends on having a libc. +//! - `alloc`: Enable features that depend on allocation (primarily string formatting). +//! - `std`: Enables functionality that depends on the standard library. This is a superset of +//! `alloc` and adds interoperation with standard library types and traits. +//! - `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +//! - `now`: Enables reading the system time (`now`). +//! - `wasmbind`: Interface with the JS Date API for the `wasm32` target. //! //! Optional features: //! -//! - `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project -//! - [`serde`][]: Enable serialization/deserialization via serde. -//! - `unstable-locales`: Enable localization. This adds various methods with a -//! `_localized` suffix. The implementation and API may change or even be -//! removed in a patch release. Feedback welcome. +//! - `serde`: Enable serialization/deserialization via [serde]. +//! - `rkyv`: Deprecated, use the `rkyv-*` features. +//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], +//! using 16-bit integers for integral `*size` types. +//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], +//! using 32-bit integers for integral `*size` types. +//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], +//! using 64-bit integers for integral `*size` types. +//! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. +//! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. +//! - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. +//! The implementation and API may change or even be removed in a patch release. Feedback welcome. +//! - `oldtime`: This feature no longer has any effect; it used to offer compatibility with the +//! `time` 0.1 crate. +//! +//! Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. +//! +//! See the [cargo docs] for examples of specifying features. +//! +//! [serde]: https://github.com/serde-rs/serde +//! [rkyv]: https://github.com/rkyv/rkyv +//! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features //! -//! [`serde`]: https://github.com/serde-rs/serde -//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen +//! ## Overview //! -//! See the [cargo docs][] for examples of specifying features. +//! ### Time delta / Duration //! -//! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features +//! Chrono has a [`TimeDelta`] type to represent the magnitude of a time span. This is an "accurate" +//! duration represented as seconds and nanoseconds, and does not represent "nominal" components +//! such as days or months. //! -//! ## Overview +//! The [`TimeDelta`] type was previously named `Duration` (and is still available as a type alias +//! with that name). A notable difference with the similar [`core::time::Duration`] is that it is a +//! signed value instead of unsigned. //! -//! ### Duration -//! -//! Chrono currently uses its own [`Duration`] type to represent the magnitude -//! of a time span. Since this has the same name as the newer, standard type for -//! duration, the reference will refer this type as `OldDuration`. -//! -//! Note that this is an "accurate" duration represented as seconds and -//! nanoseconds and does not represent "nominal" components such as days or -//! months. -//! -//! When the `oldtime` feature is enabled, [`Duration`] is an alias for the -//! [`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -//! type from v0.1 of the time crate. time v0.1 is deprecated, so new code -//! should disable the `oldtime` feature and use the `chrono::Duration` type -//! instead. The `oldtime` feature is enabled by default for backwards -//! compatibility, but future versions of Chrono are likely to remove the -//! feature entirely. -//! -//! Chrono does not yet natively support -//! the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -//! but it will be supported in the future. -//! Meanwhile you can convert between two types with -//! [`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -//! and -//! [`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) +//! Chrono currently only supports a small number of operations with [`core::time::Duration`]. +//! You can convert between both types with the [`TimeDelta::from_std`] and [`TimeDelta::to_std`] //! methods. //! //! ### Date and Time //! -//! Chrono provides a -//! [**`DateTime`**](./struct.DateTime.html) -//! type to represent a date and a time in a timezone. +//! Chrono provides a [`DateTime`] type to represent a date and a time in a timezone. //! -//! For more abstract moment-in-time tracking such as internal timekeeping -//! that is unconcerned with timezones, consider -//! [`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -//! which tracks your system clock, or -//! [`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -//! is an opaque but monotonically-increasing representation of a moment in time. +//! For more abstract moment-in-time tracking such as internal timekeeping that is unconcerned with +//! timezones, consider [`std::time::SystemTime`], which tracks your system clock, or +//! [`std::time::Instant`], which is an opaque but monotonically-increasing representation of a +//! moment in time. //! -//! `DateTime` is timezone-aware and must be constructed from -//! the [**`TimeZone`**](./offset/trait.TimeZone.html) object, -//! which defines how the local date is converted to and back from the UTC date. -//! There are three well-known `TimeZone` implementations: +//! [`DateTime`] is timezone-aware and must be constructed from a [`TimeZone`] object, which defines +//! how the local date is converted to and back from the UTC date. +//! There are three well-known [`TimeZone`] implementations: //! -//! * [**`Utc`**](./offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. +//! * [`Utc`] specifies the UTC time zone. It is most efficient. //! -//! * [**`Local`**](./offset/struct.Local.html) specifies the system local time zone. +//! * [`Local`] specifies the system local time zone. //! -//! * [**`FixedOffset`**](./offset/struct.FixedOffset.html) specifies -//! an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. -//! This often results from the parsed textual date and time. -//! Since it stores the most information and does not depend on the system environment, -//! you would want to normalize other `TimeZone`s into this type. +//! * [`FixedOffset`] specifies an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. +//! This often results from the parsed textual date and time. Since it stores the most information +//! and does not depend on the system environment, you would want to normalize other `TimeZone`s +//! into this type. //! -//! `DateTime`s with different `TimeZone` types are distinct and do not mix, -//! but can be converted to each other using -//! the [`DateTime::with_timezone`](./struct.DateTime.html#method.with_timezone) method. +//! [`DateTime`]s with different [`TimeZone`] types are distinct and do not mix, but can be +//! converted to each other using the [`DateTime::with_timezone`] method. //! -//! You can get the current date and time in the UTC time zone -//! ([`Utc::now()`](./offset/struct.Utc.html#method.now)) -//! or in the local time zone -//! ([`Local::now()`](./offset/struct.Local.html#method.now)). +//! You can get the current date and time in the UTC time zone ([`Utc::now()`]) or in the local time +//! zone ([`Local::now()`]). //! -//! ```rust +//! ``` +//! # #[cfg(feature = "now")] { +//! use chrono::prelude::*; +//! +//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` +//! # let _ = utc; +//! # } +//! ``` +//! +//! ``` +//! # #[cfg(feature = "clock")] { //! use chrono::prelude::*; //! -//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -//! # let _ = utc; let _ = local; +//! # let _ = local; +//! # } //! ``` //! -//! Alternatively, you can create your own date and time. -//! This is a bit verbose due to Rust's lack of function and method overloading, -//! but in turn we get a rich combination of initialization methods. +//! Alternatively, you can create your own date and time. This is a bit verbose due to Rust's lack +//! of function and method overloading, but in turn we get a rich combination of initialization +//! methods. //! -//! ```rust +//! ``` +//! use chrono::offset::MappedLocalTime; //! use chrono::prelude::*; -//! use chrono::offset::LocalResult; //! -//! let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` +//! # fn doctest() -> Option<()> { +//! +//! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_opt(9, 10, 11)? +//! .and_utc() +//! ); +//! //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") -//! assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); +//! assert_eq!(dt, NaiveDate::from_yo_opt(2014, 189)?.and_hms_opt(9, 10, 11)?.and_utc()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. -//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); -//! -//! let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); +//! assert_eq!( +//! dt, +//! NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc() +//! ); +//! +//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_milli_opt(9, 10, 11, 12)? +//! .and_utc(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_micro_opt(9, 10, 11, 12_000)? +//! .and_utc() +//! ); +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_nano_opt(9, 10, 11, 12_000_000)? +//! .and_utc() +//! ); //! //! // dynamic verification -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), -//! LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -//! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); -//! +//! assert_eq!( +//! Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), +//! MappedLocalTime::Single( +//! NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc() +//! ) +//! ); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), MappedLocalTime::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), MappedLocalTime::None); +//! +//! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -//! let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -//! let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); +//! let local_dt = Local +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(), +//! ) +//! .unwrap(); +//! let fixed_dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8) +//! .unwrap() +//! .and_hms_milli_opt(18, 10, 11, 12) +//! .unwrap(), +//! ) +//! .unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; +//! # } +//! # Some(()) +//! # } +//! # doctest().unwrap(); //! ``` //! -//! Various properties are available to the date and time, and can be altered individually. -//! Most of them are defined in the traits [`Datelike`](./trait.Datelike.html) and -//! [`Timelike`](./trait.Timelike.html) which you should `use` before. +//! Various properties are available to the date and time, and can be altered individually. Most of +//! them are defined in the traits [`Datelike`] and [`Timelike`] which you should `use` before. //! Addition and subtraction is also supported. //! The following illustrates most supported operations to the date and time: //! //! ```rust -//! # extern crate chrono; -//! -//! # fn main() { //! use chrono::prelude::*; -//! use chrono::Duration; +//! use chrono::TimeDelta; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: -//! let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); +//! let dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(21, 45, 59, 324310806) +//! .unwrap(), +//! ) +//! .unwrap(); //! //! // property accessors //! assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); @@ -193,8 +227,15 @@ //! //! // time zone accessor and manipulation //! assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -//! assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -//! assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); +//! assert_eq!(dt.timezone(), FixedOffset::east_opt(9 * 3600).unwrap()); +//! assert_eq!( +//! dt.with_timezone(&Utc), +//! NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 45, 59, 324310806) +//! .unwrap() +//! .and_utc() +//! ); //! //! // a sample of property manipulations (validates dynamically) //! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -202,33 +243,36 @@ //! assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE //! //! // arithmetic operations -//! let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -//! let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); -//! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -//! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), -//! Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), -//! Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -//! # } +//! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); +//! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); +//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::try_seconds(-2 * 3600 + 2).unwrap()); +//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::try_seconds(2 * 3600 - 2).unwrap()); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! + TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap() +//! ); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! - TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap() +//! ); //! ``` //! //! ### Formatting and Parsing //! -//! Formatting is done via the [`format`](./struct.DateTime.html#method.format) method, -//! which format is equivalent to the familiar `strftime` format. +//! Formatting is done via the [`format`](DateTime::format()) method, which format is equivalent to +//! the familiar `strftime` format. //! -//! See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and list of +//! specifiers. //! //! The default `to_string` method and `{:?}` specifier also give a reasonable representation. -//! Chrono also provides [`to_rfc2822`](./struct.DateTime.html#method.to_rfc2822) and -//! [`to_rfc3339`](./struct.DateTime.html#method.to_rfc3339) methods -//! for well-known formats. +//! Chrono also provides [`to_rfc2822`](DateTime::to_rfc2822) and +//! [`to_rfc3339`](DateTime::to_rfc3339) methods for well-known formats. //! -//! Chrono now also provides date formatting in almost any language without the -//! help of an additional C library. This functionality is under the feature -//! `unstable-locales`: +//! Chrono now also provides date formatting in almost any language without the help of an +//! additional C library. This functionality is under the feature `unstable-locales`: //! //! ```toml //! chrono = { version = "0.4", features = ["unstable-locales"] } @@ -237,14 +281,18 @@ //! The `unstable-locales` feature requires and implies at least the `alloc` feature. //! //! ```rust +//! # #[allow(unused_imports)] //! use chrono::prelude::*; //! -//! # #[cfg(feature = "unstable-locales")] +//! # #[cfg(all(feature = "unstable-locales", feature = "alloc"))] //! # fn test() { -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -//! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); +//! assert_eq!( +//! dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), +//! "vendredi 28 novembre 2014, 12:00:09" +//! ); //! //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); //! assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); @@ -253,48 +301,40 @@ //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); //! //! // Note that milli/nanoseconds are only printed if they are non-zero -//! let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); +//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 0, 9, 1) +//! .unwrap() +//! .and_utc(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } -//! # #[cfg(not(feature = "unstable-locales"))] +//! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))] //! # fn test() {} -//! # if cfg!(feature = "unstable-locales") { +//! # if cfg!(all(feature = "unstable-locales", feature = "alloc")) { //! # test(); //! # } //! ``` //! -//! Parsing can be done with three methods: -//! -//! 1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait -//! (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method -//! on a string) can be used for parsing `DateTime`, `DateTime` and -//! `DateTime` values. This parses what the `{:?}` -//! ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) -//! format specifier prints, and requires the offset to be present. -//! -//! 2. [`DateTime::parse_from_str`](./struct.DateTime.html#method.parse_from_str) parses -//! a date and time with offsets and returns `DateTime`. -//! This should be used when the offset is a part of input and the caller cannot guess that. -//! It *cannot* be used when the offset can be missing. -//! [`DateTime::parse_from_rfc2822`](./struct.DateTime.html#method.parse_from_rfc2822) -//! and -//! [`DateTime::parse_from_rfc3339`](./struct.DateTime.html#method.parse_from_rfc3339) -//! are similar but for well-known formats. -//! -//! 3. [`Offset::datetime_from_str`](./offset/trait.TimeZone.html#method.datetime_from_str) is -//! similar but returns `DateTime` of given offset. -//! When the explicit offset is missing from the input, it simply uses given offset. -//! It issues an error when the input contains an explicit offset different -//! from the current offset. -//! -//! More detailed control over the parsing process is available via -//! [`format`](./format/index.html) module. +//! Parsing can be done with two methods: +//! +//! 1. The standard [`FromStr`](std::str::FromStr) trait (and [`parse`](str::parse) method on a +//! string) can be used for parsing `DateTime`, `DateTime` and +//! `DateTime` values. This parses what the `{:?}` ([`std::fmt::Debug`] format specifier +//! prints, and requires the offset to be present. +//! +//! 2. [`DateTime::parse_from_str`] parses a date and time with offsets and returns +//! `DateTime`. This should be used when the offset is a part of input and the +//! caller cannot guess that. It *cannot* be used when the offset can be missing. +//! [`DateTime::parse_from_rfc2822`] and [`DateTime::parse_from_rfc3339`] are similar but for +//! well-known formats. +//! +//! More detailed control over the parsing process is available via [`format`](mod@format) module. //! //! ```rust //! use chrono::prelude::*; //! -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -//! let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); +//! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9 * 3600).unwrap()); //! //! // method 1 //! assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); @@ -302,1247 +342,402 @@ //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); //! //! // method 2 -//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), -//! Ok(fixed_dt.clone())); -//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), -//! Ok(fixed_dt.clone())); +//! assert_eq!( +//! DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), +//! Ok(fixed_dt.clone()) +//! ); +//! assert_eq!( +//! DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), +//! Ok(fixed_dt.clone()) +//! ); //! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! -//! // method 3 -//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -//! assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); -//! //! // oops, the year is missing! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); //! // oops, the format string does not include the year at all! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); //! // oops, the weekday is incorrect! -//! assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); //! ``` //! -//! Again : See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! Again: See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and +//! list of specifiers. //! //! ### Conversion from and to EPOCH timestamps //! -//! Use [`Utc.timestamp(seconds, nanoseconds)`](./offset/trait.TimeZone.html#method.timestamp) -//! to construct a [`DateTime`](./struct.DateTime.html) from a UNIX timestamp +//! Use [`DateTime::from_timestamp(seconds, nanoseconds)`](DateTime::from_timestamp) +//! to construct a [`DateTime`] from a UNIX timestamp //! (seconds, nanoseconds that passed since January 1st 1970). //! -//! Use [`DateTime.timestamp`](./struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -//! from a [`DateTime`](./struct.DateTime.html). Additionally, you can use -//! [`DateTime.timestamp_subsec_nanos`](./struct.DateTime.html#method.timestamp_subsec_nanos) +//! Use [`DateTime.timestamp`](DateTime::timestamp) to get the timestamp (in seconds) +//! from a [`DateTime`]. Additionally, you can use +//! [`DateTime.timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos) //! to get the number of additional number of nanoseconds. //! -//! ```rust +//! ``` +//! # #[cfg(feature = "alloc")] { //! // We need the trait in scope to use Utc::timestamp(). -//! use chrono::{DateTime, TimeZone, Utc}; +//! use chrono::{DateTime, Utc}; //! //! // Construct a datetime from epoch: -//! let dt = Utc.timestamp(1_500_000_000, 0); +//! let dt: DateTime = DateTime::from_timestamp_secs(1_500_000_000).unwrap(); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: //! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); //! assert_eq!(dt.timestamp(), 1_500_000_000); +//! # } //! ``` //! -//! ### Individual date -//! -//! Chrono also provides an individual date type ([**`Date`**](./struct.Date.html)). -//! It also has time zones attached, and have to be constructed via time zones. -//! Most operations available to `DateTime` are also available to `Date` whenever appropriate. -//! -//! ```rust -//! use chrono::prelude::*; -//! use chrono::offset::LocalResult; -//! -//! # // these *may* fail, but only very rarely. just rerun the test if you were that unfortunate ;) -//! assert_eq!(Utc::today(), Utc::now().date()); -//! assert_eq!(Local::today(), Local::now().date()); -//! -//! assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); -//! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), -//! "070809"); -//! ``` -//! -//! There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. -//! -//! `DateTime` has [`date`](./struct.DateTime.html#method.date) method -//! which returns a `Date` which represents its date component. -//! There is also a [`time`](./struct.DateTime.html#method.time) method, -//! which simply returns a naive local time described below. -//! //! ### Naive date and time //! -//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -//! as [**`NaiveDate`**](./naive/struct.NaiveDate.html), -//! [**`NaiveTime`**](./naive/struct.NaiveTime.html) and -//! [**`NaiveDateTime`**](./naive/struct.NaiveDateTime.html) respectively. +//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` as +//! [`NaiveDate`], [`NaiveTime`] and [`NaiveDateTime`] respectively. //! -//! They have almost equivalent interfaces as their timezone-aware twins, -//! but are not associated to time zones obviously and can be quite low-level. -//! They are mostly useful for building blocks for higher-level types. +//! They have almost equivalent interfaces as their timezone-aware twins, but are not associated to +//! time zones obviously and can be quite low-level. They are mostly useful for building blocks for +//! higher-level types. //! //! Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -//! [`naive_local`](./struct.DateTime.html#method.naive_local) returns -//! a view to the naive local time, -//! and [`naive_utc`](./struct.DateTime.html#method.naive_utc) returns -//! a view to the naive UTC time. +//! [`naive_local`](DateTime::naive_local) returns a view to the naive local time, +//! and [`naive_utc`](DateTime::naive_utc) returns a view to the naive UTC time. //! //! ## Limitations //! -//! Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -//! Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. -//! -//! Date types are limited in about +/- 262,000 years from the common epoch. -//! Time types are limited in the nanosecond accuracy. -//! -//! [Leap seconds are supported in the representation but -//! Chrono doesn't try to make use of them](./naive/struct.NaiveTime.html#leap-second-handling). -//! (The main reason is that leap seconds are not really predictable.) -//! Almost *every* operation over the possible leap seconds will ignore them. -//! Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -//! if you want. -//! -//! Chrono inherently does not support an inaccurate or partial date and time representation. -//! Any operation that can be ambiguous will return `None` in such cases. -//! For example, "a month later" of 2014-01-30 is not well-defined -//! and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. -//! -//! Non ISO week handling is not yet supported. -//! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -//! crate ([sources](https://github.com/bcourtine/chrono-ext/)). -//! -//! Advanced time zone handling is not yet supported. -//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. - -#![doc(html_root_url = "https://docs.rs/chrono/latest/")] -#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![deny(dead_code)] -// lints are added all the time, we test on 1.13 -#![allow(unknown_lints)] +//! * Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +//! * Date types are limited to about +/- 262,000 years from the common epoch. +//! * Time types are limited to nanosecond accuracy. +//! * Leap seconds can be represented, but Chrono does not fully support them. +//! See [Leap Second Handling](NaiveTime#leap-second-handling). +//! +//! ## Rust version requirements +//! +//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. +//! +//! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done +//! lightly. +//! +//! ## Relation between chrono and time 0.1 +//! +//! Rust first had a `time` module added to `std` in its 0.7 release. It later moved to +//! `libextra`, and then to a `libtime` library shipped alongside the standard library. In 2014 +//! work on chrono started in order to provide a full-featured date and time library in Rust. +//! Some improvements from chrono made it into the standard library; notably, `chrono::Duration` +//! was included as `std::time::Duration` ([rust#15934]) in 2014. +//! +//! In preparation of Rust 1.0 at the end of 2014 `libtime` was moved out of the Rust distro and +//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), like the +//! `num` and `rand` crates. Of course chrono kept its dependency on this `time` crate. `time` +//! started re-exporting `std::time::Duration` during this period. Later, the standard library was +//! changed to have a more limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the +//! `time` crate kept the full functionality with `time::Duration`. `time::Duration` had been a +//! part of chrono's public API. +//! +//! By 2016 `time` 0.1 lived under the `rust-lang-deprecated` organisation and was not actively +//! maintained ([time#136]). chrono absorbed the platform functionality and `Duration` type of the +//! `time` crate in [chrono#478] (the work started in [chrono#286]). In order to preserve +//! compatibility with downstream crates depending on `time` and `chrono` sharing a `Duration` +//! type, chrono kept depending on time 0.1. chrono offered the option to opt out of the `time` +//! dependency by disabling the `oldtime` feature (swapping it out for an effectively similar +//! chrono type). In 2019, @jhpratt took over maintenance on the `time` crate and released what +//! amounts to a new crate as `time` 0.2. +//! +//! [rust#15934]: https://github.com/rust-lang/rust/pull/15934 +//! [rust#18832]: https://github.com/rust-lang/rust/pull/18832#issuecomment-62448221 +//! [rust#18858]: https://github.com/rust-lang/rust/pull/18858 +//! [rust#24920]: https://github.com/rust-lang/rust/pull/24920 +//! [RFC 1040]: https://rust-lang.github.io/rfcs/1040-duration-reform.html +//! [time#136]: https://github.com/time-rs/time/issues/136 +//! [chrono#286]: https://github.com/chronotope/chrono/pull/286 +//! [chrono#478]: https://github.com/chronotope/chrono/pull/478 +//! +//! ## Security advisories +//! +//! In November of 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the `time` crate. +//! @quininer had found that calls to `localtime_r` may be unsound ([chrono#499]). Eventually, almost +//! a year later, this was also made into a security advisory against chrono as [RUSTSEC-2020-0159], +//! which had platform code similar to `time`. +//! +//! On Unix-like systems a process is given a timezone id or description via the `TZ` environment +//! variable. We need this timezone data to calculate the current local time from a value that is +//! in UTC, such as the time from the system clock. `time` 0.1 and chrono used the POSIX function +//! `localtime_r` to do the conversion to local time, which reads the `TZ` variable. +//! +//! Rust assumes the environment to be writable and uses locks to access it from multiple threads. +//! Some other programming languages and libraries use similar locking strategies, but these are +//! typically not shared across languages. More importantly, POSIX declares modifying the +//! environment in a multi-threaded process as unsafe, and `getenv` in libc can't be changed to +//! take a lock because it returns a pointer to the data (see [rust#27970] for more discussion). +//! +//! Since version 4.20 chrono no longer uses `localtime_r`, instead using Rust code to query the +//! timezone (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data +//! from the system timezone database directly. The code for this was forked from the [tz-rs crate] +//! by @x-hgg-x. As such, chrono now respects the Rust lock when reading the `TZ` environment +//! variable. In general, code should avoid modifying the environment. +//! +//! [CVE-2020-26235]: https://nvd.nist.gov/vuln/detail/CVE-2020-26235 +//! [RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071 +//! [chrono#499]: https://github.com/chronotope/chrono/pull/499 +//! [RUSTSEC-2020-0159]: https://rustsec.org/advisories/RUSTSEC-2020-0159.html +//! [rust#27970]: https://github.com/rust-lang/rust/issues/27970 +//! [chrono#677]: https://github.com/chronotope/chrono/pull/677 +//! [tz-rs crate]: https://crates.io/crates/tz-rs +//! +//! ## Removing time 0.1 +//! +//! Because time 0.1 has been unmaintained for years, however, the security advisory mentioned +//! above has not been addressed. While chrono maintainers were careful not to break backwards +//! compatibility with the `time::Duration` type, there has been a long stream of issues from +//! users inquiring about the time 0.1 dependency with the vulnerability. We investigated the +//! potential breakage of removing the time 0.1 dependency in [chrono#1095] using a crater-like +//! experiment and determined that the potential for breaking (public) dependencies is very low. +//! We reached out to those few crates that did still depend on compatibility with time 0.1. +//! +//! As such, for chrono 0.4.30 we have decided to swap out the time 0.1 `Duration` implementation +//! for a local one that will offer a strict superset of the existing API going forward. This +//! will prevent most downstream users from being affected by the security vulnerability in time +//! 0.1 while minimizing the ecosystem impact of semver-incompatible version churn. +//! +//! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 + +#![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] +#![warn(unreachable_pub)] +#![warn(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -#![cfg_attr(feature = "cargo-clippy", allow( - renamed_and_removed_lints, - // The explicit 'static lifetimes are still needed for rustc 1.13-16 - // backward compatibility, and this appeases clippy. If minimum rustc - // becomes 1.17, should be able to remove this, those 'static lifetimes, - // and use `static` in a lot of places `const` is used now. - redundant_static_lifetimes, - // the field-init shorthand (which this lint recommends) was stabilized in rust 1.17. - redundant_field_names, - // #[non_exhaustive] was introduced in 1.40 - manual_non_exhaustive, - // `matches!` was stabilized in 1.42 - match_like_matches_macro, - // Changing trivially_copy_pass_by_ref would require an incompatible version - // bump. - trivially_copy_pass_by_ref, - try_err, - // Currently deprecated, we use the separate implementation to add docs - // warning that putting a time in a hash table is probably a bad idea - derive_hash_xor_eq, - // Range contains was stabilized in 1.35. - manual_range_contains, -))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(all(feature = "std", not(feature = "alloc")))] -extern crate std as alloc; -#[cfg(any(feature = "std", test))] -extern crate std as core; -#[cfg(feature = "oldtime")] -extern crate time as oldtime; -#[cfg(not(feature = "oldtime"))] -mod oldtime; +mod time_delta; +#[doc(no_inline)] +#[cfg(any(feature = "std", feature = "core-error"))] +pub use time_delta::OutOfRangeError; +pub use time_delta::TimeDelta; -#[cfg(feature = "clock")] -extern crate libc; -#[cfg(all(feature = "clock", windows))] -extern crate winapi; -#[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) -))] -mod sys; +/// Alias of [`TimeDelta`]. +pub type Duration = TimeDelta; -extern crate num_integer; -extern crate num_traits; -#[cfg(feature = "rustc-serialize")] -extern crate rustc_serialize; -#[cfg(feature = "serde")] -extern crate serde as serdelib; -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -#[macro_use] -extern crate doc_comment; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate js_sys; -#[cfg(feature = "unstable-locales")] -extern crate pure_rust_locales; -#[cfg(feature = "bench")] -extern crate test; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate wasm_bindgen; +use core::fmt; -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -doctest!("../README.md"); +/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). +pub mod prelude { + #[allow(deprecated)] + pub use crate::Date; + #[cfg(feature = "clock")] + pub use crate::Local; + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + pub use crate::Locale; + pub use crate::SubsecRound; + pub use crate::{DateTime, SecondsFormat}; + pub use crate::{Datelike, Month, Timelike, Weekday}; + pub use crate::{FixedOffset, Utc}; + pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; + pub use crate::{Offset, TimeZone}; +} -// this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::Duration; +mod date; +#[allow(deprecated)] +pub use date::Date; +#[doc(no_inline)] +#[allow(deprecated)] +pub use date::{MAX_DATE, MIN_DATE}; -pub use date::{Date, MAX_DATE, MIN_DATE}; -#[cfg(feature = "rustc-serialize")] -pub use datetime::rustc_serialize::TsSeconds; -pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; +mod datetime; +pub use datetime::DateTime; +#[allow(deprecated)] +#[doc(no_inline)] +pub use datetime::{MAX_DATETIME, MIN_DATETIME}; + +pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use format::Locale; -pub use format::{ParseError, ParseResult}; -#[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -#[doc(no_inline)] -pub use offset::Local; -#[doc(no_inline)] -pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; -pub use round::{DurationRound, RoundingError, SubsecRound}; +pub use format::{ParseError, ParseResult, SecondsFormat}; -/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). -pub mod prelude { - #[doc(no_inline)] - pub use Date; - #[cfg(feature = "clock")] - #[doc(no_inline)] - pub use Local; - #[cfg(feature = "unstable-locales")] - #[doc(no_inline)] - pub use Locale; - #[doc(no_inline)] - pub use SubsecRound; - #[doc(no_inline)] - pub use {DateTime, SecondsFormat}; - #[doc(no_inline)] - pub use {Datelike, Month, Timelike, Weekday}; - #[doc(no_inline)] - pub use {FixedOffset, Utc}; - #[doc(no_inline)] - pub use {NaiveDate, NaiveDateTime, NaiveTime}; - #[doc(no_inline)] - pub use {Offset, TimeZone}; -} +pub mod naive; +#[doc(inline)] +pub use naive::{Days, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveWeek}; -// useful throughout the codebase -macro_rules! try_opt { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - -mod div; pub mod offset; -pub mod naive { - //! Date and time types unconcerned with timezones. - //! - //! They are primarily building blocks for other types - //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), - //! but can be also used for the simpler date and time handling. +#[cfg(feature = "clock")] +#[doc(inline)] +pub use offset::Local; +#[doc(hidden)] +pub use offset::LocalResult; +pub use offset::MappedLocalTime; +#[doc(inline)] +pub use offset::{FixedOffset, Offset, TimeZone, Utc}; - mod date; - mod datetime; - mod internals; - mod isoweek; - mod time; +pub mod round; +pub use round::{DurationRound, RoundingError, SubsecRound}; - pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; - #[cfg(feature = "rustc-serialize")] - #[allow(deprecated)] - pub use self::datetime::rustc_serialize::TsSeconds; - pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; - pub use self::isoweek::IsoWeek; - pub use self::time::NaiveTime; +mod weekday; +#[doc(no_inline)] +pub use weekday::ParseWeekdayError; +pub use weekday::Weekday; - #[cfg(feature = "__internal_bench")] - #[doc(hidden)] - pub use self::internals::YearFlags as __BenchYearFlags; +mod weekday_set; +pub use weekday_set::WeekdaySet; - /// Serialization/Deserialization of naive types in alternate formats - /// - /// The various modules in here are intended to be used with serde's [`with` - /// annotation][1] to serialize as something other than the default [RFC - /// 3339][2] format. - /// - /// [1]: https://serde.rs/attributes.html#field-attributes - /// [2]: https://tools.ietf.org/html/rfc3339 - #[cfg(feature = "serde")] - pub mod serde { - pub use super::datetime::serde::*; - } -} -mod date; -mod datetime; -pub mod format; -mod round; +mod month; +#[doc(no_inline)] +pub use month::ParseMonthError; +pub use month::{Month, Months}; + +mod traits; +pub use traits::{Datelike, Timelike}; #[cfg(feature = "__internal_bench")] #[doc(hidden)] pub use naive::__BenchYearFlags; -/// Serialization/Deserialization in alternate formats +/// Serialization/Deserialization with serde +/// +/// The [`DateTime`] type has default implementations for (de)serializing to/from the [RFC 3339] +/// format. This module provides alternatives for serializing to timestamps. /// -/// The various modules in here are intended to be used with serde's [`with` -/// annotation][1] to serialize as something other than the default [RFC -/// 3339][2] format. +/// The alternatives are for use with serde's [`with` annotation] combined with the module name. +/// Alternatively the individual `serialize` and `deserialize` functions in each module can be used +/// with serde's [`serialize_with`] and [`deserialize_with`] annotations. /// -/// [1]: https://serde.rs/attributes.html#field-attributes -/// [2]: https://tools.ietf.org/html/rfc3339 +/// *Available on crate feature 'serde' only.* +/// +/// [RFC 3339]: https://tools.ietf.org/html/rfc3339 +/// [`with` annotation]: https://serde.rs/field-attrs.html#with +/// [`serialize_with`]: https://serde.rs/field-attrs.html#serialize_with +/// [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with #[cfg(feature = "serde")] pub mod serde { - pub use super::datetime::serde::*; -} - -// Until rust 1.18 there is no "pub(crate)" so to share this we need it in the root + use core::fmt; + use serde::de; -#[cfg(feature = "serde")] -enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, -} + pub use super::datetime::serde::*; -/// Construct a [`SerdeError::NonExistent`] -#[cfg(feature = "serde")] -fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } -} + /// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`. + pub(crate) fn invalid_ts(value: T) -> E + where + E: de::Error, + T: fmt::Display, + { + E::custom(SerdeError::InvalidTimestamp(value)) + } -#[cfg(feature = "serde")] -impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) + enum SerdeError { + InvalidTimestamp(T), } -} -// impl core::error::Error for SerdeError {} -#[cfg(feature = "serde")] -impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &SerdeError::NonExistent { ref timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) + impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SerdeError::InvalidTimestamp(ts) => { + write!(f, "value is not a legal timestamp: {ts}") + } } - &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), } } } -/// The day of week. +/// Zero-copy serialization/deserialization with rkyv. /// -/// The order of the days of week depends on the context. -/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) -/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Weekday { - /// Monday. - Mon = 0, - /// Tuesday. - Tue = 1, - /// Wednesday. - Wed = 2, - /// Thursday. - Thu = 3, - /// Friday. - Fri = 4, - /// Saturday. - Sat = 5, - /// Sunday. - Sun = 6, -} - -impl Weekday { - /// The next day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` - #[inline] - pub fn succ(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Tue, - Weekday::Tue => Weekday::Wed, - Weekday::Wed => Weekday::Thu, - Weekday::Thu => Weekday::Fri, - Weekday::Fri => Weekday::Sat, - Weekday::Sat => Weekday::Sun, - Weekday::Sun => Weekday::Mon, - } - } - - /// The previous day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` - #[inline] - pub fn pred(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Sun, - Weekday::Tue => Weekday::Mon, - Weekday::Wed => Weekday::Tue, - Weekday::Thu => Weekday::Wed, - Weekday::Fri => Weekday::Thu, - Weekday::Sat => Weekday::Fri, - Weekday::Sun => Weekday::Sat, - } - } - - /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 - #[inline] - pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } - } - - /// Returns a day-of-week number starting from Sunday = 1. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 - #[inline] - pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } - } - - /// Returns a day-of-week number starting from Monday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 - #[inline] - pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } - } +/// This module re-exports the `Archived*` versions of chrono's types. +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +pub mod rkyv { + pub use crate::datetime::ArchivedDateTime; + pub use crate::month::ArchivedMonth; + pub use crate::naive::date::ArchivedNaiveDate; + pub use crate::naive::datetime::ArchivedNaiveDateTime; + pub use crate::naive::isoweek::ArchivedIsoWeek; + pub use crate::naive::time::ArchivedNaiveTime; + pub use crate::offset::fixed::ArchivedFixedOffset; + #[cfg(feature = "clock")] + pub use crate::offset::local::ArchivedLocal; + pub use crate::offset::utc::ArchivedUtc; + pub use crate::time_delta::ArchivedTimeDelta; + pub use crate::weekday::ArchivedWeekday; - /// Returns a day-of-week number starting from Sunday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 - #[inline] - pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } - } + /// Alias of [`ArchivedTimeDelta`] + pub type ArchivedDuration = ArchivedTimeDelta; } -impl fmt::Display for Weekday { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Weekday::Mon => "Mon", - Weekday::Tue => "Tue", - Weekday::Wed => "Wed", - Weekday::Thu => "Thu", - Weekday::Fri => "Fri", - Weekday::Sat => "Sat", - Weekday::Sun => "Sun", - }) - } +/// Out of range error type used in various converting APIs +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct OutOfRange { + _private: (), } -/// Any weekday can be represented as an integer from 0 to 6, which equals to -/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. -/// Do not heavily depend on this though; use explicit methods whenever possible. -impl num_traits::FromPrimitive for Weekday { - #[inline] - fn from_i64(n: i64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } +impl OutOfRange { + const fn new() -> OutOfRange { + OutOfRange { _private: () } } - - #[inline] - fn from_u64(n: u64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } - } -} - -use core::fmt; - -/// An error resulting from reading `Weekday` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseWeekdayError { - _dummy: (), } -impl fmt::Debug for ParseWeekdayError { +impl fmt::Display for OutOfRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseWeekdayError {{ .. }}") - } -} - -// the actual `FromStr` implementation is in the `format` module to leverage the existing code - -#[cfg(feature = "serde")] -mod weekday_serde { - use super::Weekday; - use core::fmt; - use serdelib::{de, ser}; - - impl ser::Serialize for Weekday { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct WeekdayVisitor; - - impl<'de> de::Visitor<'de> for WeekdayVisitor { - type Value = Weekday; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Weekday") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short or long weekday names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Weekday { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(WeekdayVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Weekday::*; - - let cases: Vec<(Weekday, &str)> = vec![ - (Mon, "\"Mon\""), - (Tue, "\"Tue\""), - (Wed, "\"Wed\""), - (Thu, "\"Thu\""), - (Fri, "\"Fri\""), - (Sat, "\"Sat\""), - (Sun, "\"Sun\""), - ]; - - for (weekday, expected_str) in cases { - let string = to_string(&weekday).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Weekday::*; - - let cases: Vec<(&str, Weekday)> = vec![ - ("\"mon\"", Mon), - ("\"MONDAY\"", Mon), - ("\"MonDay\"", Mon), - ("\"mOn\"", Mon), - ("\"tue\"", Tue), - ("\"tuesday\"", Tue), - ("\"wed\"", Wed), - ("\"wednesday\"", Wed), - ("\"thu\"", Thu), - ("\"thursday\"", Thu), - ("\"fri\"", Fri), - ("\"friday\"", Fri), - ("\"sat\"", Sat), - ("\"saturday\"", Sat), - ("\"sun\"", Sun), - ("\"sunday\"", Sun), - ]; - - for (str, expected_weekday) in cases { - let weekday = from_str::(str).unwrap(); - assert_eq!(weekday, expected_weekday); - } - - let errors: Vec<&str> = - vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; - - for str in errors { - from_str::(str).unwrap_err(); - } - } -} - -/// The month of the year. -/// -/// This enum is just a convenience implementation. -/// The month in dates created by DateLike objects does not return this enum. -/// -/// It is possible to convert from a date to a month independently -/// ``` -/// # extern crate num_traits; -/// use num_traits::FromPrimitive; -/// use chrono::prelude::*; -/// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); -/// // `2019-10-28T09:10:11Z` -/// let month = Month::from_u32(date.month()); -/// assert_eq!(month, Some(Month::October)) -/// ``` -/// Or from a Month to an integer usable by dates -/// ``` -/// # use chrono::prelude::*; -/// let month = Month::January; -/// let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); -/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); -/// ``` -/// Allows mapping from and to month, from 1-January to 12-December. -/// Can be Serialized/Deserialized with serde -// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Month { - /// January - January = 0, - /// February - February = 1, - /// March - March = 2, - /// April - April = 3, - /// May - May = 4, - /// June - June = 5, - /// July - July = 6, - /// August - August = 7, - /// September - September = 8, - /// October - October = 9, - /// November - November = 10, - /// December - December = 11, -} - -impl Month { - /// The next month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `February` | `March` | `...` | `January` - #[inline] - pub fn succ(&self) -> Month { - match *self { - Month::January => Month::February, - Month::February => Month::March, - Month::March => Month::April, - Month::April => Month::May, - Month::May => Month::June, - Month::June => Month::July, - Month::July => Month::August, - Month::August => Month::September, - Month::September => Month::October, - Month::October => Month::November, - Month::November => Month::December, - Month::December => Month::January, - } + write!(f, "out of range") } - - /// The previous month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `December` | `January` | `...` | `November` - #[inline] - pub fn pred(&self) -> Month { - match *self { - Month::January => Month::December, - Month::February => Month::January, - Month::March => Month::February, - Month::April => Month::March, - Month::May => Month::April, - Month::June => Month::May, - Month::July => Month::June, - Month::August => Month::July, - Month::September => Month::August, - Month::October => Month::September, - Month::November => Month::October, - Month::December => Month::November, - } - } - - /// Returns a month-of-year number starting from January = 1. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// -------------------------| --------- | ---------- | --- | ----- - /// `m.number_from_month()`: | 1 | 2 | `...` | 12 - #[inline] - pub fn number_from_month(&self) -> u32 { - match *self { - Month::January => 1, - Month::February => 2, - Month::March => 3, - Month::April => 4, - Month::May => 5, - Month::June => 6, - Month::July => 7, - Month::August => 8, - Month::September => 9, - Month::October => 10, - Month::November => 11, - Month::December => 12, - } - } - - /// Get the name of the month - /// - /// ``` - /// use chrono::Month; - /// - /// assert_eq!(Month::January.name(), "January") - /// ``` - pub fn name(&self) -> &'static str { - match *self { - Month::January => "January", - Month::February => "February", - Month::March => "March", - Month::April => "April", - Month::May => "May", - Month::June => "June", - Month::July => "July", - Month::August => "August", - Month::September => "September", - Month::October => "October", - Month::November => "November", - Month::December => "December", - } - } -} - -impl num_traits::FromPrimitive for Month { - /// Returns an Option from a i64, assuming a 1-index, January = 1. - /// - /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` - /// ---------------------------| -------------------- | --------------------- | ... | ----- - /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) - - #[inline] - fn from_u64(n: u64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_i64(n: i64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_u32(n: u32) -> Option { - match n { - 1 => Some(Month::January), - 2 => Some(Month::February), - 3 => Some(Month::March), - 4 => Some(Month::April), - 5 => Some(Month::May), - 6 => Some(Month::June), - 7 => Some(Month::July), - 8 => Some(Month::August), - 9 => Some(Month::September), - 10 => Some(Month::October), - 11 => Some(Month::November), - 12 => Some(Month::December), - _ => None, - } - } -} - -/// An error resulting from reading `` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseMonthError { - _dummy: (), } -impl fmt::Debug for ParseMonthError { +impl fmt::Debug for OutOfRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseMonthError {{ .. }}") + write!(f, "out of range") } } -#[cfg(feature = "serde")] -mod month_serde { - use super::Month; - use serdelib::{de, ser}; - - use core::fmt; - - impl ser::Serialize for Month { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(self.name()) - } - } - - struct MonthVisitor; - - impl<'de> de::Visitor<'de> for MonthVisitor { - type Value = Month; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Month") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Month { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(MonthVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Month::*; - - let cases: Vec<(Month, &str)> = vec![ - (January, "\"January\""), - (February, "\"February\""), - (March, "\"March\""), - (April, "\"April\""), - (May, "\"May\""), - (June, "\"June\""), - (July, "\"July\""), - (August, "\"August\""), - (September, "\"September\""), - (October, "\"October\""), - (November, "\"November\""), - (December, "\"December\""), - ]; - - for (month, expected_str) in cases { - let string = to_string(&month).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Month::*; - - let cases: Vec<(&str, Month)> = vec![ - ("\"january\"", January), - ("\"jan\"", January), - ("\"FeB\"", February), - ("\"MAR\"", March), - ("\"mar\"", March), - ("\"april\"", April), - ("\"may\"", May), - ("\"june\"", June), - ("\"JULY\"", July), - ("\"august\"", August), - ("\"september\"", September), - ("\"October\"", October), - ("\"November\"", November), - ("\"DECEmbEr\"", December), - ]; - - for (string, expected_month) in cases { - let month = from_str::(string).unwrap(); - assert_eq!(month, expected_month); - } - - let errors: Vec<&str> = - vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; - - for string in errors { - from_str::(string).unwrap_err(); - } +#[cfg(feature = "defmt")] +impl defmt::Format for OutOfRange { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "out of range"); } } -/// The common set of methods for date component. -pub trait Datelike: Sized { - /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). - fn year(&self) -> i32; - - /// Returns the absolute year number starting from 1 with a boolean flag, - /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). - #[inline] - fn year_ce(&self) -> (bool, u32) { - let year = self.year(); - if year < 1 { - (false, (1 - year) as u32) - } else { - (true, year as u32) - } - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - fn month(&self) -> u32; - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - fn month0(&self) -> u32; - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - fn day(&self) -> u32; +#[cfg(feature = "std")] +impl std::error::Error for OutOfRange {} - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - fn day0(&self) -> u32; +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for OutOfRange {} - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - fn ordinal(&self) -> u32; - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - fn ordinal0(&self) -> u32; - - /// Returns the day of week. - fn weekday(&self) -> Weekday; - - /// Returns the ISO week. - fn iso_week(&self) -> IsoWeek; - - /// Makes a new value with the year number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_year(&self, year: i32) -> Option; - - /// Makes a new value with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month(&self, month: u32) -> Option; - - /// Makes a new value with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month0(&self, month0: u32) -> Option; - - /// Makes a new value with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day(&self, day: u32) -> Option; - - /// Makes a new value with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day0(&self, day0: u32) -> Option; - - /// Makes a new value with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal(&self, ordinal: u32) -> Option; - - /// Makes a new value with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal0(&self, ordinal0: u32) -> Option; - - /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. - /// - /// # Examples - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(1970, 1, 1).num_days_from_ce(), 719_163); - /// assert_eq!(NaiveDate::from_ymd(2, 1, 1).num_days_from_ce(), 366); - /// assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - /// assert_eq!(NaiveDate::from_ymd(0, 1, 1).num_days_from_ce(), -365); - /// ``` - fn num_days_from_ce(&self) -> i32 { - // See test_num_days_from_ce_against_alternative_impl below for a more straightforward - // implementation. - - // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. - let mut year = self.year() - 1; - let mut ndays = 0; - if year < 0 { - let excess = 1 + (-year) / 400; - year += excess * 400; - ndays -= excess * 146_097; +/// Workaround because `?` is not (yet) available in const context. +#[macro_export] +#[doc(hidden)] +macro_rules! try_opt { + ($e:expr) => { + match $e { + Some(v) => v, + None => return None, } - let div_100 = year / 100; - ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); - ndays + self.ordinal() as i32 - } + }; } -/// The common set of methods for time component. -pub trait Timelike: Sized { - /// Returns the hour number from 0 to 23. - fn hour(&self) -> u32; - - /// Returns the hour number from 1 to 12 with a boolean flag, - /// which is false for AM and true for PM. - #[inline] - fn hour12(&self) -> (bool, u32) { - let hour = self.hour(); - let mut hour12 = hour % 12; - if hour12 == 0 { - hour12 = 12; - } - (hour >= 12, hour12) - } - - /// Returns the minute number from 0 to 59. - fn minute(&self) -> u32; - - /// Returns the second number from 0 to 59. - fn second(&self) -> u32; - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). - fn nanosecond(&self) -> u32; - - /// Makes a new value with the hour number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_hour(&self, hour: u32) -> Option; - - /// Makes a new value with the minute number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_minute(&self, min: u32) -> Option; - - /// Makes a new value with the second number changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`second`](#tymethod.second) method, - /// the input range is restricted to 0 through 59. - fn with_second(&self, sec: u32) -> Option; - - /// Makes a new value with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`nanosecond`](#tymethod.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - fn with_nanosecond(&self, nano: u32) -> Option; - - /// Returns the number of non-leap seconds past the last midnight. - #[inline] - fn num_seconds_from_midnight(&self) -> u32 { - self.hour() * 3600 + self.minute() * 60 + self.second() +/// Workaround because `.expect()` is not (yet) available in const context. +pub(crate) const fn expect(opt: Option, msg: &str) -> T { + match opt { + Some(val) => val, + None => panic!("{}", msg), } } #[cfg(test)] -extern crate num_iter; - -mod test { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_readme_doomsday() { - use num_iter::range_inclusive; - - for y in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - // even months - let d4 = NaiveDate::from_ymd(y, 4, 4); - let d6 = NaiveDate::from_ymd(y, 6, 6); - let d8 = NaiveDate::from_ymd(y, 8, 8); - let d10 = NaiveDate::from_ymd(y, 10, 10); - let d12 = NaiveDate::from_ymd(y, 12, 12); - - // nine to five, seven-eleven - let d59 = NaiveDate::from_ymd(y, 5, 9); - let d95 = NaiveDate::from_ymd(y, 9, 5); - let d711 = NaiveDate::from_ymd(y, 7, 11); - let d117 = NaiveDate::from_ymd(y, 11, 7); - - // "March 0" - let d30 = NaiveDate::from_ymd(y, 3, 1).pred(); - - let weekday = d30.weekday(); - let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; - assert!(other_dates.iter().all(|d| d.weekday() == weekday)); - } - } +mod tests { + #[cfg(feature = "clock")] + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; #[test] - fn test_month_enum_primitive_parse() { - use num_traits::FromPrimitive; - - let jan_opt = Month::from_u32(1); - let feb_opt = Month::from_u64(2); - let dec_opt = Month::from_i64(12); - let no_month = Month::from_u32(13); - assert_eq!(jan_opt, Some(Month::January)); - assert_eq!(feb_opt, Some(Month::February)); - assert_eq!(dec_opt, Some(Month::December)); - assert_eq!(no_month, None); - - let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); - assert_eq!(Month::from_u32(date.month()), Some(Month::October)); - - let month = Month::January; - let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); - assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); - } -} - -/// Tests `Datelike::num_days_from_ce` against an alternative implementation. -/// -/// The alternative implementation is not as short as the current one but it is simpler to -/// understand, with less unexplained magic constants. -#[test] -fn test_num_days_from_ce_against_alternative_impl() { - /// Returns the number of multiples of `div` in the range `start..end`. - /// - /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the - /// behaviour is defined by the following equation: - /// `in_between(start, end, div) == - in_between(end, start, div)`. - /// - /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. - /// - /// # Panics - /// - /// Panics if `div` is not positive. - fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); - let start = (start.div_euclid(div), start.rem_euclid(div)); - let end = (end.div_euclid(div), end.rem_euclid(div)); - // The lowest multiple of `div` greater than or equal to `start`, divided. - let start = start.0 + (start.1 != 0) as i32; - // The lowest multiple of `div` greater than or equal to `end`, divided. - let end = end.0 + (end.1 != 0) as i32; - end - start - } - - /// Alternative implementation to `Datelike::num_days_from_ce` - fn num_days_from_ce(date: &Date) -> i32 { - let year = date.year(); - let diff = move |div| in_between(1, year, div); - // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all - // the multiples of 4 except multiples of 100 but including multiples of 400. - date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) - } - - use num_iter::range_inclusive; - - for year in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - let jan1_year = NaiveDate::from_ymd(year, 1, 1); - assert_eq!( - jan1_year.num_days_from_ce(), - num_days_from_ce(&jan1_year), - "on {:?}", - jan1_year - ); - let mid_year = jan1_year + Duration::days(133); - assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {:?}", mid_year); + #[allow(deprecated)] + #[cfg(feature = "clock")] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 4); + assert_eq!(size_of::>(), 4); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::(), 12); + assert_eq!(size_of::>(), 12); + + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>>(), 16); } } - -#[test] -fn test_month_enum_succ_pred() { - assert_eq!(Month::January.succ(), Month::February); - assert_eq!(Month::December.succ(), Month::January); - assert_eq!(Month::January.pred(), Month::December); - assert_eq!(Month::February.pred(), Month::January); -} diff --git a/src/month.rs b/src/month.rs new file mode 100644 index 0000000000..5e51d7f73c --- /dev/null +++ b/src/month.rs @@ -0,0 +1,484 @@ +use core::fmt; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::OutOfRange; +use crate::naive::NaiveDate; + +/// The month of the year. +/// +/// This enum is just a convenience implementation. +/// The month in dates created by DateLike objects does not return this enum. +/// +/// It is possible to convert from a date to a month independently +/// ``` +/// use chrono::prelude::*; +/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); +/// // `2019-10-28T09:10:11Z` +/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok(); +/// assert_eq!(month, Some(Month::October)) +/// ``` +/// Or from a Month to an integer usable by dates +/// ``` +/// # use chrono::prelude::*; +/// let month = Month::January; +/// let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); +/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); +/// ``` +/// Allows mapping from and to month, from 1-January to 12-December. +/// Can be Serialized/Deserialized with serde +// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Month { + /// January + January = 0, + /// February + February = 1, + /// March + March = 2, + /// April + April = 3, + /// May + May = 4, + /// June + June = 5, + /// July + July = 6, + /// August + August = 7, + /// September + September = 8, + /// October + October = 9, + /// November + November = 10, + /// December + December = 11, +} + +impl Month { + /// The next month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.succ()`: | `February` | `March` | `...` | `January` + #[inline] + #[must_use] + pub const fn succ(&self) -> Month { + match *self { + Month::January => Month::February, + Month::February => Month::March, + Month::March => Month::April, + Month::April => Month::May, + Month::May => Month::June, + Month::June => Month::July, + Month::July => Month::August, + Month::August => Month::September, + Month::September => Month::October, + Month::October => Month::November, + Month::November => Month::December, + Month::December => Month::January, + } + } + + /// The previous month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.pred()`: | `December` | `January` | `...` | `November` + #[inline] + #[must_use] + pub const fn pred(&self) -> Month { + match *self { + Month::January => Month::December, + Month::February => Month::January, + Month::March => Month::February, + Month::April => Month::March, + Month::May => Month::April, + Month::June => Month::May, + Month::July => Month::June, + Month::August => Month::July, + Month::September => Month::August, + Month::October => Month::September, + Month::November => Month::October, + Month::December => Month::November, + } + } + + /// Returns a month-of-year number starting from January = 1. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// -------------------------| --------- | ---------- | --- | ----- + /// `m.number_from_month()`: | 1 | 2 | `...` | 12 + #[inline] + #[must_use] + pub const fn number_from_month(&self) -> u32 { + match *self { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + } + } + + /// Get the name of the month + /// + /// ``` + /// use chrono::Month; + /// + /// assert_eq!(Month::January.name(), "January") + /// ``` + #[must_use] + pub const fn name(&self) -> &'static str { + match *self { + Month::January => "January", + Month::February => "February", + Month::March => "March", + Month::April => "April", + Month::May => "May", + Month::June => "June", + Month::July => "July", + Month::August => "August", + Month::September => "September", + Month::October => "October", + Month::November => "November", + Month::December => "December", + } + } + + /// Get the length in days of the month + /// + /// Yields `None` if `year` is out of range for `NaiveDate`. + pub fn num_days(&self, year: i32) -> Option { + Some(match *self { + Month::January => 31, + Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() { + true => 29, + false => 28, + }, + Month::March => 31, + Month::April => 30, + Month::May => 31, + Month::June => 30, + Month::July => 31, + Month::August => 31, + Month::September => 30, + Month::October => 31, + Month::November => 30, + Month::December => 31, + }) + } +} + +impl TryFrom for Month { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(OutOfRange::new()), + } + } +} + +impl num_traits::FromPrimitive for Month { + /// Returns an `Option` from a i64, assuming a 1-index, January = 1. + /// + /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + /// ---------------------------| -------------------- | --------------------- | ... | ----- + /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) + #[inline] + fn from_u64(n: u64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_i64(n: i64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_u32(n: u32) -> Option { + match n { + 1 => Some(Month::January), + 2 => Some(Month::February), + 3 => Some(Month::March), + 4 => Some(Month::April), + 5 => Some(Month::May), + 6 => Some(Month::June), + 7 => Some(Month::July), + 8 => Some(Month::August), + 9 => Some(Month::September), + 10 => Some(Month::October), + 11 => Some(Month::November), + 12 => Some(Month::December), + _ => None, + } + } +} + +/// A duration in calendar months +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Months(pub(crate) u32); + +impl Months { + /// Construct a new `Months` from a number of months + pub const fn new(num: u32) -> Self { + Self(num) + } + + /// Returns the total number of months in the `Months` instance. + #[inline] + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +/// An error resulting from reading `` value with `FromStr`. +#[derive(Clone, PartialEq, Eq)] +pub struct ParseMonthError { + pub(crate) _dummy: (), +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseMonthError {} + +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for ParseMonthError {} + +impl fmt::Display for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + +impl fmt::Debug for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ParseMonthError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "ParseMonthError {{ .. }}") + } +} + +#[cfg(feature = "serde")] +mod month_serde { + use super::Month; + use serde::{de, ser}; + + use core::fmt; + + impl ser::Serialize for Month { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(self.name()) + } + } + + struct MonthVisitor; + + impl de::Visitor<'_> for MonthVisitor { + type Value = Month; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Month") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Month { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(MonthVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::Month; + use crate::{Datelike, Months, OutOfRange, TimeZone, Utc}; + + #[test] + fn test_month_enum_try_from() { + assert_eq!(Month::try_from(1), Ok(Month::January)); + assert_eq!(Month::try_from(2), Ok(Month::February)); + assert_eq!(Month::try_from(12), Ok(Month::December)); + assert_eq!(Month::try_from(13), Err(OutOfRange::new())); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October)); + + let month = Month::January; + let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } + + #[test] + fn test_month_enum_primitive_parse() { + use num_traits::FromPrimitive; + + let jan_opt = Month::from_u32(1); + let feb_opt = Month::from_u64(2); + let dec_opt = Month::from_i64(12); + let no_month = Month::from_u32(13); + assert_eq!(jan_opt, Some(Month::January)); + assert_eq!(feb_opt, Some(Month::February)); + assert_eq!(dec_opt, Some(Month::December)); + assert_eq!(no_month, None); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::from_u32(date.month()), Some(Month::October)); + + let month = Month::January; + let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } + + #[test] + fn test_month_enum_succ_pred() { + assert_eq!(Month::January.succ(), Month::February); + assert_eq!(Month::December.succ(), Month::January); + assert_eq!(Month::January.pred(), Month::December); + assert_eq!(Month::February.pred(), Month::January); + } + + #[test] + fn test_month_partial_ord() { + assert!(Month::January <= Month::January); + assert!(Month::January < Month::February); + assert!(Month::January < Month::December); + assert!(Month::July >= Month::May); + assert!(Month::September > Month::March); + } + + #[test] + fn test_months_as_u32() { + assert_eq!(Months::new(0).as_u32(), 0); + assert_eq!(Months::new(1).as_u32(), 1); + assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialize() { + use Month::*; + use serde_json::to_string; + + let cases: Vec<(Month, &str)> = vec![ + (January, "\"January\""), + (February, "\"February\""), + (March, "\"March\""), + (April, "\"April\""), + (May, "\"May\""), + (June, "\"June\""), + (July, "\"July\""), + (August, "\"August\""), + (September, "\"September\""), + (October, "\"October\""), + (November, "\"November\""), + (December, "\"December\""), + ]; + + for (month, expected_str) in cases { + let string = to_string(&month).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialize() { + use Month::*; + use serde_json::from_str; + + let cases: Vec<(&str, Month)> = vec![ + ("\"january\"", January), + ("\"jan\"", January), + ("\"FeB\"", February), + ("\"MAR\"", March), + ("\"mar\"", March), + ("\"april\"", April), + ("\"may\"", May), + ("\"june\"", June), + ("\"JULY\"", July), + ("\"august\"", August), + ("\"september\"", September), + ("\"October\"", October), + ("\"November\"", November), + ("\"DECEmbEr\"", December), + ]; + + for (string, expected_month) in cases { + let month = from_str::(string).unwrap(); + assert_eq!(month, expected_month); + } + + let errors: Vec<&str> = + vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; + + for string in errors { + from_str::(string).unwrap_err(); + } + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let month = Month::January; + let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), month); + } + + #[test] + fn num_days() { + assert_eq!(Month::January.num_days(2020), Some(31)); + assert_eq!(Month::February.num_days(2020), Some(29)); + assert_eq!(Month::February.num_days(2019), Some(28)); + } +} diff --git a/src/naive/date.rs b/src/naive/date.rs deleted file mode 100644 index 9cd00e7d71..0000000000 --- a/src/naive/date.rs +++ /dev/null @@ -1,2400 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 calendar date without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, str}; -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Item, Numeric, Pad}; -use naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use {Datelike, Weekday}; - -use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; -use super::isoweek; - -const MAX_YEAR: i32 = internals::MAX_YEAR; -const MIN_YEAR: i32 = internals::MIN_YEAR; - -// MAX_YEAR-12-31 minus 0000-01-01 -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days -// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days -#[cfg(test)] // only used for testing -const MAX_DAYS_FROM_YEAR_0: i32 = - MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365; - -// MIN_YEAR-01-01 minus 0000-01-01 -// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01 -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01) -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days -// -// n is set to 1000 for convenience. -#[cfg(test)] // only used for testing -const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4 - - (MIN_YEAR + 400_000) / 100 - + (MIN_YEAR + 400_000) / 400 - - 146_097_000; - -#[cfg(test)] // only used for testing, but duplicated in naive::datetime -const MAX_BITS: usize = 44; - -/// ISO 8601 calendar date without timezone. -/// Allows for every [proleptic Gregorian date](#calendar-date) -/// from Jan 1, 262145 BCE to Dec 31, 262143 CE. -/// Also supports the conversion from ISO 8601 ordinal and week date. -/// -/// # Calendar Date -/// -/// The ISO 8601 **calendar date** follows the proleptic Gregorian calendar. -/// It is like a normal civil calendar but note some slight differences: -/// -/// * Dates before the Gregorian calendar's inception in 1582 are defined via the extrapolation. -/// Be careful, as historical dates are often noted in the Julian calendar and others -/// and the transition to Gregorian may differ across countries (as late as early 20C). -/// -/// (Some example: Both Shakespeare from Britain and Cervantes from Spain seemingly died -/// on the same calendar date---April 23, 1616---but in the different calendar. -/// Britain used the Julian calendar at that time, so Shakespeare's death is later.) -/// -/// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). -/// If you need a typical BCE/BC and CE/AD notation for year numbers, -/// use the [`Datelike::year_ce`](../trait.Datelike.html#method.year_ce) method. -/// -/// # Week Date -/// -/// The ISO 8601 **week date** is a triple of year number, week number -/// and [day of the week](../enum.Weekday.html) with the following rules: -/// -/// * A week consists of Monday through Sunday, and is always numbered within some year. -/// The week number ranges from 1 to 52 or 53 depending on the year. -/// -/// * The week 1 of given year is defined as the first week containing January 4 of that year, -/// or equivalently, the first week containing four or more days in that year. -/// -/// * The year number in the week date may *not* correspond to the actual Gregorian year. -/// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. -/// -/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), -/// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and -/// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods -/// can be used to get the corresponding week date. -/// -/// # Ordinal Date -/// -/// The ISO 8601 **ordinal date** is a pair of year number and day of the year ("ordinal"). -/// The ordinal number ranges from 1 to 365 or 366 depending on the year. -/// The year number is the same as that of the [calendar date](#calendar-date). -/// -/// This is currently the internal format of Chrono's date types. -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveDate { - ymdf: DateImpl, // (year << 13) | of -} - -/// The minimum possible `NaiveDate` (January 1, 262145 BCE). -pub const MIN_DATE: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; -/// The maximum possible `NaiveDate` (December 31, 262143 CE). -pub const MAX_DATE: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; - -// as it is hard to verify year flags in `MIN_DATE` and `MAX_DATE`, -// we use a separate run-time test. -#[test] -fn test_date_bounds() { - let calculated_min = NaiveDate::from_ymd(MIN_YEAR, 1, 1); - let calculated_max = NaiveDate::from_ymd(MAX_YEAR, 12, 31); - assert!( - MIN_DATE == calculated_min, - "`MIN_DATE` should have a year flag {:?}", - calculated_min.of().flags() - ); - assert!( - MAX_DATE == calculated_max, - "`MAX_DATE` should have a year flag {:?}", - calculated_max.of().flags() - ); - - // let's also check that the entire range do not exceed 2^44 seconds - // (sometimes used for bounding `Duration` against overflow) - let maxsecs = MAX_DATE.signed_duration_since(MIN_DATE).num_seconds(); - let maxsecs = maxsecs + 86401; // also take care of DateTime - assert!( - maxsecs < (1 << MAX_BITS), - "The entire `NaiveDate` range somehow exceeds 2^{} seconds", - MAX_BITS - ); -} - -impl NaiveDate { - /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. - fn from_of(year: i32, of: Of) -> Option { - if year >= MIN_YEAR && year <= MAX_YEAR && of.valid() { - let Of(of) = of; - Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) }) - } else { - None - } - } - - /// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification. - fn from_mdf(year: i32, mdf: Mdf) -> Option { - NaiveDate::from_of(year, mdf.to_of()) - } - - /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) - /// (year, month and day). - /// - /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 3, 14); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` - pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { - NaiveDate::from_ymd_opt(year, month, day).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) - /// (year, month and day). - /// - /// Returns `None` on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let from_ymd_opt = NaiveDate::from_ymd_opt; - /// - /// assert!(from_ymd_opt(2015, 3, 14).is_some()); - /// assert!(from_ymd_opt(2015, 0, 14).is_none()); - /// assert!(from_ymd_opt(2015, 2, 29).is_none()); - /// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year - /// assert!(from_ymd_opt(400000, 1, 1).is_none()); - /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); - /// ``` - pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { - let flags = YearFlags::from_year(year); - NaiveDate::from_mdf(year, Mdf::new(month, day, flags)) - } - - /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) - /// (year and day of the year). - /// - /// Panics on the out-of-range date and/or invalid day of year. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_yo(2015, 73); - /// assert_eq!(d.ordinal(), 73); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` - pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate { - NaiveDate::from_yo_opt(year, ordinal).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) - /// (year and day of the year). - /// - /// Returns `None` on the out-of-range date and/or invalid day of year. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let from_yo_opt = NaiveDate::from_yo_opt; - /// - /// assert!(from_yo_opt(2015, 100).is_some()); - /// assert!(from_yo_opt(2015, 0).is_none()); - /// assert!(from_yo_opt(2015, 365).is_some()); - /// assert!(from_yo_opt(2015, 366).is_none()); - /// assert!(from_yo_opt(-4, 366).is_some()); // 5 BCE is a leap year - /// assert!(from_yo_opt(400000, 1).is_none()); - /// assert!(from_yo_opt(-400000, 1).is_none()); - /// ``` - pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { - let flags = YearFlags::from_year(year); - NaiveDate::from_of(year, Of::new(ordinal, flags)) - } - - /// Makes a new `NaiveDate` from the [ISO week date](#week-date) - /// (year, week number and day of the week). - /// The resulting `NaiveDate` may have a different year from the input year. - /// - /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_isoywd(2015, 11, Weekday::Sat); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` - pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { - NaiveDate::from_isoywd_opt(year, week, weekday).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [ISO week date](#week-date) - /// (year, week number and day of the week). - /// The resulting `NaiveDate` may have a different year from the input year. - /// - /// Returns `None` on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; - /// - /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); - /// assert_eq!(from_isoywd_opt(2015, 10, Weekday::Sun), Some(from_ymd(2015, 3, 8))); - /// assert_eq!(from_isoywd_opt(2015, 30, Weekday::Mon), Some(from_ymd(2015, 7, 20))); - /// assert_eq!(from_isoywd_opt(2015, 60, Weekday::Mon), None); - /// - /// assert_eq!(from_isoywd_opt(400000, 10, Weekday::Fri), None); - /// assert_eq!(from_isoywd_opt(-400000, 10, Weekday::Sat), None); - /// ``` - /// - /// The year number of ISO week date may differ from that of the calendar date. - /// - /// ``` - /// # use chrono::{NaiveDate, Weekday}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; - /// // Mo Tu We Th Fr Sa Su - /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, - /// // 2015-W01 29 30 31 1 2 3 4 <- so this is the first week - /// assert_eq!(from_isoywd_opt(2014, 52, Weekday::Sun), Some(from_ymd(2014, 12, 28))); - /// assert_eq!(from_isoywd_opt(2014, 53, Weekday::Mon), None); - /// assert_eq!(from_isoywd_opt(2015, 1, Weekday::Mon), Some(from_ymd(2014, 12, 29))); - /// - /// // 2015-W52 21 22 23 24 25 26 27 has 4+ days of old year, - /// // 2015-W53 28 29 30 31 1 2 3 <- so this is the last week - /// // 2016-W01 4 5 6 7 8 9 10 - /// assert_eq!(from_isoywd_opt(2015, 52, Weekday::Sun), Some(from_ymd(2015, 12, 27))); - /// assert_eq!(from_isoywd_opt(2015, 53, Weekday::Sun), Some(from_ymd(2016, 1, 3))); - /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); - /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); - /// ``` - pub fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { - let flags = YearFlags::from_year(year); - let nweeks = flags.nisoweeks(); - if 1 <= week && week <= nweeks { - // ordinal = week ordinal - delta - let weekord = week * 7 + weekday as u32; - let delta = flags.isoweek_delta(); - if weekord <= delta { - // ordinal < 1, previous year - let prevflags = YearFlags::from_year(year - 1); - NaiveDate::from_of( - year - 1, - Of::new(weekord + prevflags.ndays() - delta, prevflags), - ) - } else { - let ordinal = weekord - delta; - let ndays = flags.ndays(); - if ordinal <= ndays { - // this year - NaiveDate::from_of(year, Of::new(ordinal, flags)) - } else { - // ordinal > ndays, next year - let nextflags = YearFlags::from_year(year + 1); - NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)) - } - } - } else { - None - } - } - - /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with - /// January 1, 1 being day 1. - /// - /// Panics if the date is out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_num_days_from_ce(735671); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// ``` - /// - /// While not directly supported by Chrono, - /// it is easy to convert from the Julian day number - /// (January 1, 4713 BCE in the *Julian* calendar being Day 0) - /// to Gregorian with this method. - /// (Note that this panics when `jd` is out of range.) - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// fn jd_to_date(jd: i32) -> NaiveDate { - /// // keep in mind that the Julian day number is 0-based - /// // while this method requires an 1-based number. - /// NaiveDate::from_num_days_from_ce(jd - 1721425) - /// } - /// - /// // January 1, 4713 BCE in Julian = November 24, 4714 BCE in Gregorian - /// assert_eq!(jd_to_date(0), NaiveDate::from_ymd(-4713, 11, 24)); - /// - /// assert_eq!(jd_to_date(1721426), NaiveDate::from_ymd(1, 1, 1)); - /// assert_eq!(jd_to_date(2450000), NaiveDate::from_ymd(1995, 10, 9)); - /// assert_eq!(jd_to_date(2451545), NaiveDate::from_ymd(2000, 1, 1)); - /// ``` - #[inline] - pub fn from_num_days_from_ce(days: i32) -> NaiveDate { - NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") - } - - /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with - /// January 1, 1 being day 1. - /// - /// Returns `None` if the date is out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; - /// let from_ymd = NaiveDate::from_ymd; - /// - /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); - /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); - /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); - /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); - /// assert_eq!(from_ndays_opt(100_000_000), None); - /// assert_eq!(from_ndays_opt(-100_000_000), None); - /// ``` - pub fn from_num_days_from_ce_opt(days: i32) -> Option { - let days = days + 365; // make December 31, 1 BCE equal to day 0 - let (year_div_400, cycle) = div_mod_floor(days, 146_097); - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March - /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. - /// - /// # Panics - /// - /// The resulting `NaiveDate` is guaranteed to be in `month`. If `n` is larger than the number - /// of `weekday` in `month` (eg. the 6th Friday of March 2017) then this function will panic. - /// - /// `n` is 1-indexed. Passing `n=0` will cause a panic. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let from_weekday_of_month = NaiveDate::from_weekday_of_month; - /// let from_ymd = NaiveDate::from_ymd; - /// - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Wed, 1), from_ymd(2018, 8, 1)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 1), from_ymd(2018, 8, 3)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Tue, 2), from_ymd(2018, 8, 14)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 4), from_ymd(2018, 8, 24)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 5), from_ymd(2018, 8, 31)); - /// ``` - pub fn from_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u8) -> NaiveDate { - NaiveDate::from_weekday_of_month_opt(year, month, weekday, n).expect("out-of-range date") - } - - /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March - /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. `n` is 1-indexed. - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), - /// NaiveDate::from_ymd_opt(2017, 3, 10)) - /// ``` - /// - /// Returns `None` if `n` out-of-range; ie. if `n` is larger than the number of `weekday` in - /// `month` (eg. the 6th Friday of March 2017), or if `n == 0`. - pub fn from_weekday_of_month_opt( - year: i32, - month: u32, - weekday: Weekday, - n: u8, - ) -> Option { - if n == 0 { - return None; - } - let first = NaiveDate::from_ymd(year, month, 1).weekday(); - let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; - let day = (u32::from(n) - 1) * 7 + first_to_dow + 1; - NaiveDate::from_ymd_opt(year, month, day) - } - - /// Parses a string with the specified format string and returns a new `NaiveDate`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let parse_from_str = NaiveDate::parse_from_str; - /// - /// assert_eq!(parse_from_str("2015-09-05", "%Y-%m-%d"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); - /// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); - /// ``` - /// - /// Time and offset is ignored for the purpose of parsing. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17))); - /// ``` - /// - /// Out-of-bound dates or insufficient fields are errors. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert!(parse_from_str("2015/9", "%Y/%m").is_err()); - /// assert!(parse_from_str("2015/9/31", "%Y/%m/%d").is_err()); - /// ``` - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_date() - } - - /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); - /// - /// let dt: NaiveDateTime = d.and_time(t); - /// assert_eq!(dt.date(), d); - /// assert_eq!(dt.time(), t); - /// ``` - #[inline] - pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime { - NaiveDateTime::new(*self, time) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. - /// - /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; - /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. - /// - /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms(12, 34, 56); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// ``` - #[inline] - pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { - self.and_hms_opt(hour, min, sec).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. - /// - /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; - /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. - /// - /// Returns `None` on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_opt(12, 34, 56).is_some()); - /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead - /// assert!(d.and_hms_opt(12, 60, 56).is_none()); - /// assert!(d.and_hms_opt(24, 34, 56).is_none()); - /// ``` - #[inline] - pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { - NaiveTime::from_hms_opt(hour, min, sec).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_000_000); - /// ``` - #[inline] - pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { - self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); - /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second - /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); - /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); - /// ``` - #[inline] - pub fn and_hms_milli_opt( - &self, - hour: u32, - min: u32, - sec: u32, - milli: u32, - ) -> Option { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_micro(12, 34, 56, 789_012); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_012_000); - /// ``` - #[inline] - pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { - self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); - /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second - /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); - /// ``` - #[inline] - pub fn and_hms_micro_opt( - &self, - hour: u32, - min: u32, - sec: u32, - micro: u32, - ) -> Option { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_nano(12, 34, 56, 789_012_345); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_012_345); - /// ``` - #[inline] - pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { - self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); - /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second - /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); - /// ``` - #[inline] - pub fn and_hms_nano_opt( - &self, - hour: u32, - min: u32, - sec: u32, - nano: u32, - ) -> Option { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).map(|time| self.and_time(time)) - } - - /// Returns the packed month-day-flags. - #[inline] - fn mdf(&self) -> Mdf { - self.of().to_mdf() - } - - /// Returns the packed ordinal-flags. - #[inline] - fn of(&self) -> Of { - Of((self.ymdf & 0b1_1111_1111_1111) as u32) - } - - /// Makes a new `NaiveDate` with the packed month-day-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - #[inline] - fn with_mdf(&self, mdf: Mdf) -> Option { - self.with_of(mdf.to_of()) - } - - /// Makes a new `NaiveDate` with the packed ordinal-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - #[inline] - fn with_of(&self, of: Of) -> Option { - if of.valid() { - let Of(of) = of; - Some(NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of as DateImpl }) - } else { - None - } - } - - /// Makes a new `NaiveDate` for the next calendar date. - /// - /// Panics when `self` is the last representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ(), NaiveDate::from_ymd(2015, 6, 4)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 30).succ(), NaiveDate::from_ymd(2015, 7, 1)); - /// assert_eq!(NaiveDate::from_ymd(2015, 12, 31).succ(), NaiveDate::from_ymd(2016, 1, 1)); - /// ``` - #[inline] - pub fn succ(&self) -> NaiveDate { - self.succ_opt().expect("out of bound") - } - - /// Makes a new `NaiveDate` for the next calendar date. - /// - /// Returns `None` when `self` is the last representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// use chrono::naive::MAX_DATE; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 4))); - /// assert_eq!(MAX_DATE.succ_opt(), None); - /// ``` - #[inline] - pub fn succ_opt(&self) -> Option { - self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1)) - } - - /// Makes a new `NaiveDate` for the previous calendar date. - /// - /// Panics when `self` is the first representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred(), NaiveDate::from_ymd(2015, 6, 2)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 1).pred(), NaiveDate::from_ymd(2015, 5, 31)); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).pred(), NaiveDate::from_ymd(2014, 12, 31)); - /// ``` - #[inline] - pub fn pred(&self) -> NaiveDate { - self.pred_opt().expect("out of bound") - } - - /// Makes a new `NaiveDate` for the previous calendar date. - /// - /// Returns `None` when `self` is the first representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// use chrono::naive::MIN_DATE; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 2))); - /// assert_eq!(MIN_DATE.pred_opt(), None); - /// ``` - #[inline] - pub fn pred_opt(&self) -> Option { - self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31)) - } - - /// Adds the `days` part of given `Duration` to the current date. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MAX_DATE; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.checked_add_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); - /// assert_eq!(d.checked_add_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); - /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MAX_DATE.checked_add_signed(Duration::days(1)), None); - /// # } - /// ``` - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_add(try_opt!(rhs.num_days().to_i32()))); - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Subtracts the `days` part of given `Duration` from the current date. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MIN_DATE; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.checked_sub_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); - /// assert_eq!(d.checked_sub_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); - /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MIN_DATE.checked_sub_signed(Duration::days(1)), None); - /// # } - /// ``` - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_sub(try_opt!(rhs.num_days().to_i32()))); - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Subtracts another `NaiveDate` from the current date. - /// Returns a `Duration` of integral numbers. - /// - /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// let since = NaiveDate::signed_duration_since; - /// - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), Duration::zero()); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), Duration::days(1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), Duration::days(-1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), Duration::days(100)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), Duration::days(365)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), Duration::days(365*4 + 1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); - /// # } - /// ``` - pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { - let year1 = self.year(); - let year2 = rhs.year(); - let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); - let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = i64::from(internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal())); - let cycle2 = i64::from(internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal())); - OldDuration::days( - (i64::from(year1_div_400) - i64::from(year2_div_400)) * 146_097 + (cycle1 - cycle2), - ) - } - - /// Formats the date with the specified formatting items. - /// Otherwise it is the same as the ordinary `format` method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%Y-%m-%d"); - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); - /// # let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(Some(*self), None, items) - } - - /// Formats the date with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); - /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); - /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Returns an iterator that steps by days until the last representable date. - /// - /// # Example - /// - /// ``` - /// # use chrono::NaiveDate; - /// - /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 2, 28), - /// NaiveDate::from_ymd(2016, 2, 29), - /// NaiveDate::from_ymd(2016, 3, 1), - /// ]; - /// - /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_days().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; - /// } - /// assert_eq!(count, 4); - /// ``` - #[inline] - pub fn iter_days(&self) -> NaiveDateDaysIterator { - NaiveDateDaysIterator { value: *self } - } - - /// Returns an iterator that steps by weeks until the last representable date. - /// - /// # Example - /// - /// ``` - /// # use chrono::NaiveDate; - /// - /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 3, 5), - /// NaiveDate::from_ymd(2016, 3, 12), - /// NaiveDate::from_ymd(2016, 3, 19), - /// ]; - /// - /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_weeks().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; - /// } - /// assert_eq!(count, 4); - /// ``` - #[inline] - pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { - NaiveDateWeeksIterator { value: *self } - } -} - -impl Datelike for NaiveDate { - /// Returns the year number in the [calendar date](#calendar-date). - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).year(), 2015); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).year(), -308); // 309 BCE - /// ``` - #[inline] - fn year(&self) -> i32 { - self.ymdf >> 13 - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month(), 9); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month(), 3); - /// ``` - #[inline] - fn month(&self) -> u32 { - self.mdf().month() - } - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month0(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month0(), 2); - /// ``` - #[inline] - fn month0(&self) -> u32 { - self.mdf().month() - 1 - } - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day(), 14); - /// ``` - /// - /// Combined with [`NaiveDate::pred`](#method.pred), - /// one can determine the number of days in a particular month. - /// (Note that this panics when `year` is out of range.) - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// fn ndays_in_month(year: i32, month: u32) -> u32 { - /// // the first day of the next month... - /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; - /// let d = NaiveDate::from_ymd(y, m, 1); - /// - /// // ...is preceded by the last day of the original month - /// d.pred().day() - /// } - /// - /// assert_eq!(ndays_in_month(2015, 8), 31); - /// assert_eq!(ndays_in_month(2015, 9), 30); - /// assert_eq!(ndays_in_month(2015, 12), 31); - /// assert_eq!(ndays_in_month(2016, 2), 29); - /// assert_eq!(ndays_in_month(2017, 2), 28); - /// ``` - #[inline] - fn day(&self) -> u32 { - self.mdf().day() - } - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day0(), 7); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day0(), 13); - /// ``` - #[inline] - fn day0(&self) -> u32 { - self.mdf().day() - 1 - } - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal(), 251); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal(), 74); - /// ``` - /// - /// Combined with [`NaiveDate::pred`](#method.pred), - /// one can determine the number of days in a particular year. - /// (Note that this panics when `year` is out of range.) - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// fn ndays_in_year(year: i32) -> u32 { - /// // the first day of the next year... - /// let d = NaiveDate::from_ymd(year + 1, 1, 1); - /// - /// // ...is preceded by the last day of the original year - /// d.pred().ordinal() - /// } - /// - /// assert_eq!(ndays_in_year(2015), 365); - /// assert_eq!(ndays_in_year(2016), 366); - /// assert_eq!(ndays_in_year(2017), 365); - /// assert_eq!(ndays_in_year(2000), 366); - /// assert_eq!(ndays_in_year(2100), 365); - /// ``` - #[inline] - fn ordinal(&self) -> u32 { - self.of().ordinal() - } - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal0(), 250); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal0(), 73); - /// ``` - #[inline] - fn ordinal0(&self) -> u32 { - self.of().ordinal() - 1 - } - - /// Returns the day of week. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).weekday(), Weekday::Tue); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).weekday(), Weekday::Fri); - /// ``` - #[inline] - fn weekday(&self) -> Weekday { - self.of().weekday() - } - - #[inline] - fn iso_week(&self) -> IsoWeek { - isoweek::iso_week_from_yof(self.year(), self.of()) - } - - /// Makes a new `NaiveDate` with the year number changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(2016), - /// Some(NaiveDate::from_ymd(2016, 9, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(-308), - /// Some(NaiveDate::from_ymd(-308, 9, 8))); - /// ``` - /// - /// A leap day (February 29) is a good example that this method can return `None`. - /// - /// ``` - /// # use chrono::{NaiveDate, Datelike}; - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2015).is_none()); - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2020).is_some()); - /// ``` - #[inline] - fn with_year(&self, year: i32) -> Option { - // we need to operate with `mdf` since we should keep the month and day number as is - let mdf = self.mdf(); - - // adjust the flags as needed - let flags = YearFlags::from_year(year); - let mdf = mdf.with_flags(flags); - - NaiveDate::from_mdf(year, mdf) - } - - /// Makes a new `NaiveDate` with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(10), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(13), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month(2), None); // no February 30 - /// ``` - #[inline] - fn with_month(&self, month: u32) -> Option { - self.with_mdf(self.mdf().with_month(month)) - } - - /// Makes a new `NaiveDate` with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(9), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(12), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month0(1), None); // no February 30 - /// ``` - #[inline] - fn with_month0(&self, month0: u32) -> Option { - self.with_mdf(self.mdf().with_month(month0 + 1)) - } - - /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(30), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(31), - /// None); // no September 31 - /// ``` - #[inline] - fn with_day(&self, day: u32) -> Option { - self.with_mdf(self.mdf().with_day(day)) - } - - /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(29), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(30), - /// None); // no September 31 - /// ``` - #[inline] - fn with_day0(&self, day0: u32) -> Option { - self.with_mdf(self.mdf().with_day(day0 + 1)) - } - - /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(366), - /// None); // 2015 had only 365 days - /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); - /// ``` - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal)) - } - - /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(365), - /// None); // 2015 had only 365 days - /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); - /// ``` - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal0 + 1)) - } -} - -/// An addition of `Duration` to `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_add_signed`](#method.checked_add_signed) to detect that. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(-1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(364), from_ymd(2014, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*4 + 1), from_ymd(2018, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*400 + 97), from_ymd(2414, 1, 1)); -/// # } -/// ``` -impl Add for NaiveDate { - type Output = NaiveDate; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveDate { - self.checked_add_signed(rhs).expect("`NaiveDate + Duration` overflowed") - } -} - -impl AddAssign for NaiveDate { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. -/// It is the same as the addition with a negated `Duration`. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_sub_signed`](#method.checked_sub_signed) to detect that. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(-1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(364), from_ymd(2013, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*4 + 1), from_ymd(2010, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*400 + 97), from_ymd(1614, 1, 1)); -/// # } -/// ``` -impl Sub for NaiveDate { - type Output = NaiveDate; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDate { - self.checked_sub_signed(rhs).expect("`NaiveDate - Duration` overflowed") - } -} - -impl SubAssign for NaiveDate { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveDate` from the current date. -/// Returns a `Duration` of integral numbers. -/// -/// This does not overflow or underflow at all, -/// as all possible output fits in the range of `Duration`. -/// -/// The implementation is a wrapper around -/// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), Duration::zero()); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), Duration::days(1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), Duration::days(-1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), Duration::days(100)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), Duration::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), Duration::days(365*4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), Duration::days(365*400 + 97)); -/// # } -/// ``` -impl Sub for NaiveDate { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveDate) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// Iterator over `NaiveDate` with a step size of one day. -#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] -pub struct NaiveDateDaysIterator { - value: NaiveDate, -} - -impl Iterator for NaiveDateDaysIterator { - type Item = NaiveDate; - - fn next(&mut self) -> Option { - if self.value == MAX_DATE { - return None; - } - // current < MAX_DATE from here on: - let current = self.value; - // This can't panic because current is < MAX_DATE: - self.value = current.succ(); - Some(current) - } - - fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_days(); - (exact_size as usize, Some(exact_size as usize)) - } -} - -impl ExactSizeIterator for NaiveDateDaysIterator {} - -#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] -pub struct NaiveDateWeeksIterator { - value: NaiveDate, -} - -impl Iterator for NaiveDateWeeksIterator { - type Item = NaiveDate; - - fn next(&mut self) -> Option { - if MAX_DATE - self.value < OldDuration::weeks(1) { - return None; - } - let current = self.value; - self.value = current + OldDuration::weeks(1); - Some(current) - } - - fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_weeks(); - (exact_size as usize, Some(exact_size as usize)) - } -} - -impl ExactSizeIterator for NaiveDateWeeksIterator {} - -// TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator, -// TrustedLen, and Step once they becomes stable. -// See: https://github.com/chronotope/chrono/issues/208 - -/// The `Debug` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveDate; -/// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); -/// ``` -/// -/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. -/// -/// ``` -/// # use chrono::NaiveDate; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); -/// ``` -impl fmt::Debug for NaiveDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let year = self.year(); - let mdf = self.mdf(); - if 0 <= year && year <= 9999 { - write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day()) - } else { - // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day()) - } - } -} - -/// The `Display` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveDate; -/// -/// assert_eq!(format!("{}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); -/// ``` -/// -/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. -/// -/// ``` -/// # use chrono::NaiveDate; -/// assert_eq!(format!("{}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); -/// ``` -impl fmt::Display for NaiveDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -/// Parsing a `str` into a `NaiveDate` uses the same format, -/// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`. -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveDate; -/// -/// let d = NaiveDate::from_ymd(2015, 9, 18); -/// assert_eq!("2015-09-18".parse::(), Ok(d)); -/// -/// let d = NaiveDate::from_ymd(12345, 6, 7); -/// assert_eq!("+12345-6-7".parse::(), Ok(d)); -/// -/// assert!("foo".parse::().is_err()); -/// ``` -impl str::FromStr for NaiveDate { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_date() - } -} - -/// The default value for a NaiveDate is 1st of January 1970. -/// -/// # Example -/// -/// ```rust -/// use chrono::NaiveDate; -/// -/// let default_date = NaiveDate::default(); -/// assert_eq!(default_date, NaiveDate::from_ymd(1970, 1, 1)); -/// ``` -impl Default for NaiveDate { - fn default() -> Self { - NaiveDate::from_ymd(1970, 1, 1) - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDate) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(to_string(&NaiveDate::from_ymd(2014, 7, 24)).ok(), Some(r#""2014-07-24""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(0, 1, 1)).ok(), Some(r#""0000-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(-1, 12, 31)).ok(), Some(r#""-0001-12-31""#.into())); - assert_eq!(to_string(&MIN_DATE).ok(), Some(r#""-262144-01-01""#.into())); - assert_eq!(to_string(&MAX_DATE).ok(), Some(r#""+262143-12-31""#.into())); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - use std::{i32, i64}; - - assert_eq!(from_str(r#""2016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""+002016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31))); - assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(MIN_DATE)); - assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(MAX_DATE)); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""20001231""#).is_err()); - assert!(from_str(r#""2000-00-00""#).is_err()); - assert!(from_str(r#""2000-02-30""#).is_err()); - assert!(from_str(r#""2001-02-29""#).is_err()); - assert!(from_str(r#""2002-002-28""#).is_err()); - assert!(from_str(r#""yyyy-mm-dd""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"20.01"#).is_err()); - assert!(from_str(&i32::MIN.to_string()).is_err()); - assert!(from_str(&i32::MAX.to_string()).is_err()); - assert!(from_str(&i64::MIN.to_string()).is_err()); - assert!(from_str(&i64::MAX.to_string()).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"ymdf":20}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveDate; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveDate { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDate { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date")) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } -} - -#[cfg(feature = "serde")] -mod serde { - use super::NaiveDate; - use core::fmt; - use serdelib::{de, ser}; - - // TODO not very optimized for space (binary formats would want something better) - - impl ser::Serialize for NaiveDate { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct NaiveDateVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateVisitor { - type Value = NaiveDate; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date string") - } - - #[cfg(any(feature = "std", test))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - - #[cfg(not(any(feature = "std", test)))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveDate { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveDateVisitor) - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let d = NaiveDate::from_ymd(2014, 7, 24); - let encoded = serialize(&d, Infinite).unwrap(); - let decoded: NaiveDate = deserialize(&encoded).unwrap(); - assert_eq!(d, decoded); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveDate; - use super::{MAX_DATE, MAX_DAYS_FROM_YEAR_0, MAX_YEAR}; - use super::{MIN_DATE, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; - use oldtime::Duration; - use std::{i32, u32}; - use {Datelike, Weekday}; - - #[test] - fn test_date_from_ymd() { - let ymd_opt = NaiveDate::from_ymd_opt; - - assert!(ymd_opt(2012, 0, 1).is_none()); - assert!(ymd_opt(2012, 1, 1).is_some()); - assert!(ymd_opt(2012, 2, 29).is_some()); - assert!(ymd_opt(2014, 2, 29).is_none()); - assert!(ymd_opt(2014, 3, 0).is_none()); - assert!(ymd_opt(2014, 3, 1).is_some()); - assert!(ymd_opt(2014, 3, 31).is_some()); - assert!(ymd_opt(2014, 3, 32).is_none()); - assert!(ymd_opt(2014, 12, 31).is_some()); - assert!(ymd_opt(2014, 13, 1).is_none()); - } - - #[test] - fn test_date_from_yo() { - let yo_opt = NaiveDate::from_yo_opt; - let ymd = NaiveDate::from_ymd; - - assert_eq!(yo_opt(2012, 0), None); - assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); - assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); - assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); - assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); - assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); - assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); - assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); - assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); - assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); - assert_eq!(yo_opt(2012, 367), None); - - assert_eq!(yo_opt(2014, 0), None); - assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); - assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); - assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); - assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); - assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); - assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); - assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); - assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); - assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); - assert_eq!(yo_opt(2014, 366), None); - } - - #[test] - fn test_date_from_isoywd() { - let isoywd_opt = NaiveDate::from_isoywd_opt; - let ymd = NaiveDate::from_ymd; - - assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); - assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); - assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); - - assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); - assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); - - assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); - assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); - assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); - } - - #[test] - fn test_date_from_isoywd_and_iso_week() { - for year in 2000..2401 { - for week in 1..54 { - for &weekday in [ - Weekday::Mon, - Weekday::Tue, - Weekday::Wed, - Weekday::Thu, - Weekday::Fri, - Weekday::Sat, - Weekday::Sun, - ] - .iter() - { - let d = NaiveDate::from_isoywd_opt(year, week, weekday); - if let Some(d) = d { - assert_eq!(d.weekday(), weekday); - let w = d.iso_week(); - assert_eq!(w.year(), year); - assert_eq!(w.week(), week); - } - } - } - } - - for year in 2000..2401 { - for month in 1..13 { - for day in 1..32 { - let d = NaiveDate::from_ymd_opt(year, month, day); - if let Some(d) = d { - let w = d.iso_week(); - let d_ = NaiveDate::from_isoywd(w.year(), w.week(), d.weekday()); - assert_eq!(d, d_); - } - } - } - } - } - - #[test] - fn test_date_from_num_days_from_ce() { - let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; - assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd(1, 1, 1))); - assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd(1, 1, 2))); - assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd(1, 1, 31))); - assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd(1, 2, 1))); - assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd(1, 2, 28))); - assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd(1, 3, 1))); - assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd(1, 12, 31))); - assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd(2, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd(3, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd(4, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd(5, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd(401, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 * 5 + 1), Some(NaiveDate::from_ymd(2001, 1, 1))); - assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd(1970, 1, 1))); - assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd(0, 12, 31))); // 1 BCE - assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd(-1, 12, 31))); // 2 BCE - - for days in (-9999..10001).map(|x| x * 100) { - assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); - } - - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce()), Some(MIN_DATE)); - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce() - 1), None); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce()), Some(MAX_DATE)); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce() + 1), None); - } - - #[test] - fn test_date_from_weekday_of_month_opt() { - let ymwd = NaiveDate::from_weekday_of_month_opt; - assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd(2018, 8, 1))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd(2018, 8, 2))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd(2018, 8, 5))); - assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd(2018, 8, 6))); - assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd(2018, 8, 7))); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd(2018, 8, 8))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd(2018, 8, 12))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd(2018, 8, 16))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd(2018, 8, 23))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd(2018, 8, 30))); - assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd(2018, 8, 31))); - assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); - } - - #[test] - fn test_date_fields() { - fn check(year: i32, month: u32, day: u32, ordinal: u32) { - let d1 = NaiveDate::from_ymd(year, month, day); - assert_eq!(d1.year(), year); - assert_eq!(d1.month(), month); - assert_eq!(d1.day(), day); - assert_eq!(d1.ordinal(), ordinal); - - let d2 = NaiveDate::from_yo(year, ordinal); - assert_eq!(d2.year(), year); - assert_eq!(d2.month(), month); - assert_eq!(d2.day(), day); - assert_eq!(d2.ordinal(), ordinal); - - assert_eq!(d1, d2); - } - - check(2012, 1, 1, 1); - check(2012, 1, 2, 2); - check(2012, 2, 1, 32); - check(2012, 2, 29, 60); - check(2012, 3, 1, 61); - check(2012, 4, 9, 100); - check(2012, 7, 18, 200); - check(2012, 10, 26, 300); - check(2012, 12, 31, 366); - - check(2014, 1, 1, 1); - check(2014, 1, 2, 2); - check(2014, 2, 1, 32); - check(2014, 2, 28, 59); - check(2014, 3, 1, 60); - check(2014, 4, 10, 100); - check(2014, 7, 19, 200); - check(2014, 10, 27, 300); - check(2014, 12, 31, 365); - } - - #[test] - fn test_date_weekday() { - assert_eq!(NaiveDate::from_ymd(1582, 10, 15).weekday(), Weekday::Fri); - // May 20, 1875 = ISO 8601 reference date - assert_eq!(NaiveDate::from_ymd(1875, 5, 20).weekday(), Weekday::Thu); - assert_eq!(NaiveDate::from_ymd(2000, 1, 1).weekday(), Weekday::Sat); - } - - #[test] - fn test_date_with_fields() { - let d = NaiveDate::from_ymd(2000, 2, 29); - assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd(-400, 2, 29))); - assert_eq!(d.with_year(-100), None); - assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd(1600, 2, 29))); - assert_eq!(d.with_year(1900), None); - assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_year(2001), None); - assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd(2004, 2, 29))); - assert_eq!(d.with_year(i32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 4, 30); - assert_eq!(d.with_month(0), None); - assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd(2000, 1, 30))); - assert_eq!(d.with_month(2), None); - assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd(2000, 3, 30))); - assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd(2000, 4, 30))); - assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd(2000, 12, 30))); - assert_eq!(d.with_month(13), None); - assert_eq!(d.with_month(u32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 2, 8); - assert_eq!(d.with_day(0), None); - assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd(2000, 2, 1))); - assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_day(30), None); - assert_eq!(d.with_day(u32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 5, 5); - assert_eq!(d.with_ordinal(0), None); - assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd(2000, 1, 1))); - assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd(2000, 3, 1))); - assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd(2000, 12, 31))); - assert_eq!(d.with_ordinal(367), None); - assert_eq!(d.with_ordinal(u32::MAX), None); - } - - #[test] - fn test_date_num_days_from_ce() { - assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - - for year in -9999..10001 { - assert_eq!( - NaiveDate::from_ymd(year, 1, 1).num_days_from_ce(), - NaiveDate::from_ymd(year - 1, 12, 31).num_days_from_ce() + 1 - ); - } - } - - #[test] - fn test_date_succ() { - let ymd = NaiveDate::from_ymd; - assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); - assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); - assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); - assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(MAX_DATE.year(), 12, 31).succ_opt(), None); - } - - #[test] - fn test_date_pred() { - let ymd = NaiveDate::from_ymd; - assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); - assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); - assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); - assert_eq!(ymd(MIN_DATE.year(), 1, 1).pred_opt(), None); - } - - #[test] - fn test_date_add() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: Duration, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd(y, m, d)); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - } - - check((2014, 1, 1), Duration::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::seconds(86399), Some((2014, 1, 1))); - // always round towards zero - check((2014, 1, 1), Duration::seconds(-86399), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::days(1), Some((2014, 1, 2))); - check((2014, 1, 1), Duration::days(-1), Some((2013, 12, 31))); - check((2014, 1, 1), Duration::days(364), Some((2014, 12, 31))); - check((2014, 1, 1), Duration::days(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Duration::days(365 * 400 + 97), Some((2414, 1, 1))); - - check((-7, 1, 1), Duration::days(365 * 12 + 3), Some((5, 1, 1))); - - // overflow check - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); - check((0, 1, 1), Duration::max_value(), None); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); - check((0, 1, 1), Duration::min_value(), None); - } - - #[test] - fn test_date_sub() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Duration) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let rhs = NaiveDate::from_ymd(y2, m2, d2); - assert_eq!(lhs.signed_duration_since(rhs), diff); - assert_eq!(rhs.signed_duration_since(lhs), -diff); - } - - check((2014, 1, 1), (2014, 1, 1), Duration::zero()); - check((2014, 1, 2), (2014, 1, 1), Duration::days(1)); - check((2014, 12, 31), (2014, 1, 1), Duration::days(364)); - check((2015, 1, 3), (2014, 1, 1), Duration::days(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Duration::days(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Duration::days(365 * 400 + 97)); - - check((MAX_YEAR, 12, 31), (0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64)); - check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64)); - } - - #[test] - fn test_date_addassignment() { - let ymd = NaiveDate::from_ymd; - let mut date = ymd(2016, 10, 1); - date += Duration::days(10); - assert_eq!(date, ymd(2016, 10, 11)); - date += Duration::days(30); - assert_eq!(date, ymd(2016, 11, 10)); - } - - #[test] - fn test_date_subassignment() { - let ymd = NaiveDate::from_ymd; - let mut date = ymd(2016, 10, 11); - date -= Duration::days(10); - assert_eq!(date, ymd(2016, 10, 1)); - date -= Duration::days(2); - assert_eq!(date, ymd(2016, 9, 29)); - } - - #[test] - fn test_date_fmt() { - assert_eq!(format!("{:?}", NaiveDate::from_ymd(2012, 3, 4)), "2012-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(0, 3, 4)), "0000-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(-307, 3, 4)), "-0307-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(12345, 3, 4)), "+12345-03-04"); - - assert_eq!(NaiveDate::from_ymd(2012, 3, 4).to_string(), "2012-03-04"); - assert_eq!(NaiveDate::from_ymd(0, 3, 4).to_string(), "0000-03-04"); - assert_eq!(NaiveDate::from_ymd(-307, 3, 4).to_string(), "-0307-03-04"); - assert_eq!(NaiveDate::from_ymd(12345, 3, 4).to_string(), "+12345-03-04"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:+30?}", NaiveDate::from_ymd(1234, 5, 6)), "1234-05-06"); - assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07"); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "-0000000123456-1-2", - " -123456 - 1 - 2 ", - "-12345-1-2", - "-1234-12-31", - "-7-6-5", - "350-2-28", - "360-02-29", - "0360-02-29", - "2015-2 -18", - "+70-2-18", - "+70000-2-18", - "+00007-2-18", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds - } - - #[test] - fn test_date_parse_from_str() { - let ymd = NaiveDate::from_ymd; - assert_eq!( - NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymd(2014, 5, 7)) - ); // ignore time and offset - assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), - Ok(ymd(2015, 2, 2)) - ); - assert_eq!( - NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), - Ok(ymd(2013, 8, 9)) - ); - assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); - assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); - assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient - } - - #[test] - fn test_date_format() { - let d = NaiveDate::from_ymd(2012, 3, 4); - assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); - assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); - assert_eq!(d.format("%d,%e").to_string(), "04, 4"); - assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); - assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); - assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year - assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); - assert_eq!(d.format("%F").to_string(), "2012-03-04"); - assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); - assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // non-four-digit years - assert_eq!(NaiveDate::from_ymd(12345, 1, 1).format("%Y").to_string(), "+12345"); - assert_eq!(NaiveDate::from_ymd(1234, 1, 1).format("%Y").to_string(), "1234"); - assert_eq!(NaiveDate::from_ymd(123, 1, 1).format("%Y").to_string(), "0123"); - assert_eq!(NaiveDate::from_ymd(12, 1, 1).format("%Y").to_string(), "0012"); - assert_eq!(NaiveDate::from_ymd(1, 1, 1).format("%Y").to_string(), "0001"); - assert_eq!(NaiveDate::from_ymd(0, 1, 1).format("%Y").to_string(), "0000"); - assert_eq!(NaiveDate::from_ymd(-1, 1, 1).format("%Y").to_string(), "-0001"); - assert_eq!(NaiveDate::from_ymd(-12, 1, 1).format("%Y").to_string(), "-0012"); - assert_eq!(NaiveDate::from_ymd(-123, 1, 1).format("%Y").to_string(), "-0123"); - assert_eq!(NaiveDate::from_ymd(-1234, 1, 1).format("%Y").to_string(), "-1234"); - assert_eq!(NaiveDate::from_ymd(-12345, 1, 1).format("%Y").to_string(), "-12345"); - - // corner cases - assert_eq!( - NaiveDate::from_ymd(2007, 12, 31).format("%G,%g,%U,%W,%V").to_string(), - "2008,08,53,53,01" - ); - assert_eq!( - NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(), - "2009,09,01,00,53" - ); - } - - #[test] - fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).count(), 2); - } - - #[test] - fn test_week_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); - } -} diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs new file mode 100644 index 0000000000..e1eb371bb4 --- /dev/null +++ b/src/naive/date/mod.rs @@ -0,0 +1,2702 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 calendar date without timezone. +//! +//! The implementation is optimized for determining year, month, day and day of week. +//! +//! Format of `NaiveDate`: +//! `YYYY_YYYY_YYYY_YYYY_YYYO_OOOO_OOOO_LWWW` +//! `Y`: Year +//! `O`: Ordinal +//! `L`: leap year flag (1 = common year, 0 is leap year) +//! `W`: weekday before the first day of the year +//! `LWWW`: will also be referred to as the year flags (`F`) + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::iter::FusedIterator; +use core::num::NonZeroI32; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// L10n locales. +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use pure_rust_locales::Locale; + +use super::internals::{Mdf, YearFlags}; +use crate::datetime::UNIX_EPOCH_DAY; +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{ + Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder, + write_hundreds, +}; +use crate::month::Months; +use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; +use crate::{Datelike, TimeDelta, Weekday}; +use crate::{expect, try_opt}; + +#[cfg(test)] +mod tests; + +/// ISO 8601 calendar date without timezone. +/// Allows for every [proleptic Gregorian date] from Jan 1, 262145 BCE to Dec 31, 262143 CE. +/// Also supports the conversion from ISO 8601 ordinal and week date. +/// +/// # Calendar Date +/// +/// The ISO 8601 **calendar date** follows the proleptic Gregorian calendar. +/// It is like a normal civil calendar but note some slight differences: +/// +/// * Dates before the Gregorian calendar's inception in 1582 are defined via the extrapolation. +/// Be careful, as historical dates are often noted in the Julian calendar and others +/// and the transition to Gregorian may differ across countries (as late as early 20C). +/// +/// (Some example: Both Shakespeare from Britain and Cervantes from Spain seemingly died +/// on the same calendar date---April 23, 1616---but in the different calendar. +/// Britain used the Julian calendar at that time, so Shakespeare's death is later.) +/// +/// * ISO 8601 calendars have the year 0, which is 1 BCE (a year before 1 CE). +/// If you need a typical BCE/BC and CE/AD notation for year numbers, +/// use the [`Datelike::year_ce`] method. +/// +/// # Week Date +/// +/// The ISO 8601 **week date** is a triple of year number, week number +/// and [day of the week](Weekday) with the following rules: +/// +/// * A week consists of Monday through Sunday, and is always numbered within some year. +/// The week number ranges from 1 to 52 or 53 depending on the year. +/// +/// * The week 1 of given year is defined as the first week containing January 4 of that year, +/// or equivalently, the first week containing four or more days in that year. +/// +/// * The year number in the week date may *not* correspond to the actual Gregorian year. +/// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. +/// +/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), but +/// [`Datelike::iso_week`] and [`Datelike::weekday`] methods can be used to get the corresponding +/// week date. +/// +/// # Ordinal Date +/// +/// The ISO 8601 **ordinal date** is a pair of year number and day of the year ("ordinal"). +/// The ordinal number ranges from 1 to 365 or 366 depending on the year. +/// The year number is the same as that of the [calendar date](#calendar-date). +/// +/// This is currently the internal format of Chrono's date types. +/// +/// [proleptic Gregorian date]: crate::NaiveDate#calendar-date +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct NaiveDate { + yof: NonZeroI32, // (year << 13) | of +} + +/// The minimum possible `NaiveDate` (January 1, 262145 BCE). +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MIN instead")] +pub const MIN_DATE: NaiveDate = NaiveDate::MIN; +/// The maximum possible `NaiveDate` (December 31, 262143 CE). +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] +pub const MAX_DATE: NaiveDate = NaiveDate::MAX; + +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for NaiveDate { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; + let max_days = YearFlags::from_year(year).ndays(); + let ord = u.int_in_range(1..=max_days)?; + NaiveDate::from_yo_opt(year, ord).ok_or(arbitrary::Error::IncorrectFormat) + } +} + +impl NaiveDate { + pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { + (self.ordinal() as i32 - self.weekday().days_since(day) as i32 + 6) / 7 + } + + /// Makes a new `NaiveDate` from year, ordinal and flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_ordinal_and_flags( + year: i32, + ordinal: u32, + flags: YearFlags, + ) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + if ordinal == 0 || ordinal > 366 { + return None; // Invalid + } + debug_assert!(YearFlags::from_year(year).0 == flags.0); + let yof = (year << 13) | (ordinal << 4) as i32 | flags.0 as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate::from_yof(yof)), + false => None, // Does not exist: Ordinal 366 in a common year. + } + } + + /// Makes a new `NaiveDate` from year and packed month-day-flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_mdf(year: i32, mdf: Mdf) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + Some(NaiveDate::from_yof((year << 13) | try_opt!(mdf.ordinal_and_flags()))) + } + + /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) + /// (year, month and day). + /// + /// # Panics + /// + /// Panics if the specified calendar day does not exist, on invalid values for `month` or `day`, + /// or if `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] + #[must_use] + pub const fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { + expect(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) + /// (year, month and day). + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified calendar day does not exist (for example 2023-04-31). + /// - The value for `month` or `day` is invalid. + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ymd_opt = NaiveDate::from_ymd_opt; + /// + /// assert!(from_ymd_opt(2015, 3, 14).is_some()); + /// assert!(from_ymd_opt(2015, 0, 14).is_none()); + /// assert!(from_ymd_opt(2015, 2, 29).is_none()); + /// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year + /// assert!(from_ymd_opt(400000, 1, 1).is_none()); + /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); + /// ``` + #[must_use] + pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { + let flags = YearFlags::from_year(year); + + if let Some(mdf) = Mdf::new(month, day, flags) { + NaiveDate::from_mdf(year, mdf) + } else { + None + } + } + + /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) + /// (year and day of the year). + /// + /// # Panics + /// + /// Panics if the specified ordinal day does not exist, on invalid values for `ordinal`, or if + /// `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] + #[must_use] + pub const fn from_yo(year: i32, ordinal: u32) -> NaiveDate { + expect(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) + /// (year and day of the year). + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified ordinal day does not exist (for example 2023-366). + /// - The value for `ordinal` is invalid (for example: `0`, `400`). + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_yo_opt = NaiveDate::from_yo_opt; + /// + /// assert!(from_yo_opt(2015, 100).is_some()); + /// assert!(from_yo_opt(2015, 0).is_none()); + /// assert!(from_yo_opt(2015, 365).is_some()); + /// assert!(from_yo_opt(2015, 366).is_none()); + /// assert!(from_yo_opt(-4, 366).is_some()); // 5 BCE is a leap year + /// assert!(from_yo_opt(400000, 1).is_none()); + /// assert!(from_yo_opt(-400000, 1).is_none()); + /// ``` + #[must_use] + pub const fn from_yo_opt(year: i32, ordinal: u32) -> Option { + let flags = YearFlags::from_year(year); + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) + } + + /// Makes a new `NaiveDate` from the [ISO week date](#week-date) + /// (year, week number and day of the week). + /// The resulting `NaiveDate` may have a different year from the input year. + /// + /// # Panics + /// + /// Panics if the specified week does not exist in that year, on invalid values for `week`, or + /// if the resulting date is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] + #[must_use] + pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { + expect(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [ISO week date](#week-date) + /// (year, week number and day of the week). + /// The resulting `NaiveDate` may have a different year from the input year. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified week does not exist in that year (for example 2023 week 53). + /// - The value for `week` is invalid (for example: `0`, `60`). + /// - If the resulting date is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; + /// + /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); + /// assert_eq!(from_isoywd_opt(2015, 10, Weekday::Sun), Some(from_ymd(2015, 3, 8))); + /// assert_eq!(from_isoywd_opt(2015, 30, Weekday::Mon), Some(from_ymd(2015, 7, 20))); + /// assert_eq!(from_isoywd_opt(2015, 60, Weekday::Mon), None); + /// + /// assert_eq!(from_isoywd_opt(400000, 10, Weekday::Fri), None); + /// assert_eq!(from_isoywd_opt(-400000, 10, Weekday::Sat), None); + /// ``` + /// + /// The year number of ISO week date may differ from that of the calendar date. + /// + /// ``` + /// # use chrono::{NaiveDate, Weekday}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; + /// // Mo Tu We Th Fr Sa Su + /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, + /// // 2015-W01 29 30 31 1 2 3 4 <- so this is the first week + /// assert_eq!(from_isoywd_opt(2014, 52, Weekday::Sun), Some(from_ymd(2014, 12, 28))); + /// assert_eq!(from_isoywd_opt(2014, 53, Weekday::Mon), None); + /// assert_eq!(from_isoywd_opt(2015, 1, Weekday::Mon), Some(from_ymd(2014, 12, 29))); + /// + /// // 2015-W52 21 22 23 24 25 26 27 has 4+ days of old year, + /// // 2015-W53 28 29 30 31 1 2 3 <- so this is the last week + /// // 2016-W01 4 5 6 7 8 9 10 + /// assert_eq!(from_isoywd_opt(2015, 52, Weekday::Sun), Some(from_ymd(2015, 12, 27))); + /// assert_eq!(from_isoywd_opt(2015, 53, Weekday::Sun), Some(from_ymd(2016, 1, 3))); + /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); + /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); + /// ``` + #[must_use] + pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { + let flags = YearFlags::from_year(year); + let nweeks = flags.nisoweeks(); + if week == 0 || week > nweeks { + return None; + } + // ordinal = week ordinal - delta + let weekord = week * 7 + weekday as u32; + let delta = flags.isoweek_delta(); + let (year, ordinal, flags) = if weekord <= delta { + // ordinal < 1, previous year + // `year - 1` would overflow for `year == i32::MIN`; such a year is + // well out of range for `NaiveDate`, so return `None` as documented. + let year = match year.checked_sub(1) { + Some(year) => year, + None => return None, + }; + let prevflags = YearFlags::from_year(year); + (year, weekord + prevflags.ndays() - delta, prevflags) + } else { + let ordinal = weekord - delta; + let ndays = flags.ndays(); + if ordinal <= ndays { + // this year + (year, ordinal, flags) + } else { + // ordinal > ndays, next year + // `year + 1` would overflow for `year == i32::MAX`; such a year is + // well out of range for `NaiveDate`, so return `None` as documented. + let year = match year.checked_add(1) { + Some(year) => year, + None => return None, + }; + let nextflags = YearFlags::from_year(year); + (year, ordinal - ndays, nextflags) + } + }; + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) + } + + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1 being day 1. + /// + /// # Panics + /// + /// Panics if the date is out of range. + #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_num_days_from_ce(days: i32) -> NaiveDate { + expect(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") + } + + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1 being day 1. + /// + /// # Errors + /// + /// Returns `None` if the date is out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); + /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); + /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); + /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); + /// assert_eq!(from_ndays_opt(100_000_000), None); + /// assert_eq!(from_ndays_opt(-100_000_000), None); + /// ``` + #[must_use] + pub const fn from_num_days_from_ce_opt(days: i32) -> Option { + let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 + let year_div_400 = days.div_euclid(146_097); + let cycle = days.rem_euclid(146_097); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); + let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + } + + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1970 being day 0. + /// + /// # Errors + /// + /// Returns `None` if the date is out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ndays_opt = NaiveDate::from_epoch_days; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ndays_opt(-719_162), Some(from_ymd(1, 1, 1))); + /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1970, 1, 2))); + /// assert_eq!(from_ndays_opt(0), Some(from_ymd(1970, 1, 1))); + /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(1969, 12, 31))); + /// assert_eq!(from_ndays_opt(13036), Some(from_ymd(2005, 9, 10))); + /// assert_eq!(from_ndays_opt(100_000_000), None); + /// assert_eq!(from_ndays_opt(-100_000_000), None); + /// ``` + #[must_use] + pub const fn from_epoch_days(days: i32) -> Option { + let ce_days = try_opt!(days.checked_add(UNIX_EPOCH_DAY as i32)); + NaiveDate::from_num_days_from_ce_opt(ce_days) + } + + /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. + /// + /// `n` is 1-indexed. + /// + /// # Panics + /// + /// Panics if the specified day does not exist in that month, on invalid values for `month` or + /// `n`, or if `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")] + #[must_use] + pub const fn from_weekday_of_month( + year: i32, + month: u32, + weekday: Weekday, + n: u8, + ) -> NaiveDate { + expect(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") + } + + /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. + /// + /// `n` is 1-indexed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified day does not exist in that month (for example the 5th Monday of Apr. 2023). + /// - The value for `month` or `n` is invalid. + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// assert_eq!( + /// NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), + /// NaiveDate::from_ymd_opt(2017, 3, 10) + /// ) + /// ``` + #[must_use] + pub const fn from_weekday_of_month_opt( + year: i32, + month: u32, + weekday: Weekday, + n: u8, + ) -> Option { + if n == 0 { + return None; + } + let first = try_opt!(NaiveDate::from_ymd_opt(year, month, 1)).weekday(); + let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; + let day = (n - 1) as u32 * 7 + first_to_dow + 1; + NaiveDate::from_ymd_opt(year, month, day) + } + + /// Parses a string with the specified format string and returns a new `NaiveDate`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let parse_from_str = NaiveDate::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("2015-09-05", "%Y-%m-%d"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015", "%d%b%Y"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); + /// ``` + /// + /// Time and offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap()) + /// ); + /// ``` + /// + /// Out-of-bound dates or insufficient fields are errors. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert!(parse_from_str("2015/9", "%Y/%m").is_err()); + /// assert!(parse_from_str("2015/9/31", "%Y/%m/%d").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date() + } + + /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate}; + /// let (date, remainder) = + /// NaiveDate::parse_and_remainder("2015-02-18 trailing text", "%Y-%m-%d").unwrap(); + /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDate, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date().map(|d| (d, remainder)) + } + + /// Add a duration in [`Months`] to the date + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 20).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_months(Months::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 9, 30).unwrap()) + /// ); + /// ``` + #[must_use] + pub const fn checked_add_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + match months.0 <= i32::MAX as u32 { + true => self.diff_months(months.0 as i32), + false => None, + } + } + + /// Subtract a duration in [`Months`] from the date + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2021, 8, 20).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + match months.0 <= i32::MAX as u32 { + true => self.diff_months(-(months.0 as i32)), + false => None, + } + } + + const fn diff_months(self, months: i32) -> Option { + let months = try_opt!((self.year() * 12 + self.month() as i32 - 1).checked_add(months)); + let year = months.div_euclid(12); + let month = months.rem_euclid(12) as u32 + 1; + + // Clamp original day in case new month is shorter + let flags = YearFlags::from_year(year); + let feb_days = if flags.ndays() == 366 { 29 } else { 28 }; + let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let day_max = days[(month - 1) as usize]; + let mut day = self.day(); + if day > day_max { + day = day_max; + }; + + NaiveDate::from_ymd_opt(year, month, day) + } + + /// Add a duration in [`Days`] to the date + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_days(Days::new(9)), + /// Some(NaiveDate::from_ymd_opt(2022, 3, 1).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(1000000000000)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_add_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(days.0 as i32), + false => None, + } + } + + /// Subtract a duration in [`Days`] from the date + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(-(days.0 as i32)), + false => None, + } + } + + /// Add a duration of `i32` days to the date. + pub(crate) const fn add_days(self, days: i32) -> Option { + // Fast path if the result is within the same year. + // Also `DateTime::checked_(add|sub)_days` relies on this path, because if the value remains + // within the year it doesn't do a check if the year is in range. + // This way `DateTime:checked_(add|sub)_days(Days::new(0))` can be a no-op on dates were the + // local datetime is beyond `NaiveDate::{MIN, MAX}. + const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + if let Some(ordinal) = ((self.yof() & ORDINAL_MASK) >> 4).checked_add(days) { + if ordinal > 0 && ordinal <= (365 + self.leap_year() as i32) { + let year_and_flags = self.yof() & !ORDINAL_MASK; + return Some(NaiveDate::from_yof(year_and_flags | (ordinal << 4))); + } + } + // do the full check + let year = self.year(); + let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); + let cycle = yo_to_cycle(year_mod_400 as u32, self.ordinal()); + let cycle = try_opt!((cycle as i32).checked_add(days)); + let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); + year_div_400 += cycle_div_400y; + + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); + let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + } + + /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// + /// let dt: NaiveDateTime = d.and_time(t); + /// assert_eq!(dt.date(), d); + /// assert_eq!(dt.time(), t); + /// ``` + #[inline] + #[must_use] + pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { + NaiveDateTime::new(*self, time) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. + /// + /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; + /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. + /// + /// # Panics + /// + /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { + expect(self.and_hms_opt(hour, min, sec), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. + /// + /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; + /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute and/or second. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_opt(12, 34, 56).is_some()); + /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead + /// assert!(d.and_hms_opt(12, 60, 56).is_none()); + /// assert!(d.and_hms_opt(24, 34, 56).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { + let time = try_opt!(NaiveTime::from_hms_opt(hour, min, sec)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { + expect(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); + /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second + /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); + /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_milli_opt( + &self, + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike, Weekday}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// + /// let dt: NaiveDateTime = d.and_hms_micro_opt(12, 34, 56, 789_012).unwrap(); + /// assert_eq!(dt.year(), 2015); + /// assert_eq!(dt.weekday(), Weekday::Wed); + /// assert_eq!(dt.second(), 56); + /// assert_eq!(dt.nanosecond(), 789_012_000); + /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { + expect(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); + /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second + /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_micro_opt( + &self, + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { + expect(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); + /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second + /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_nano_opt( + &self, + hour: u32, + min: u32, + sec: u32, + nano: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano)); + Some(self.and_time(time)) + } + + /// Returns the packed month-day-flags. + #[inline] + const fn mdf(&self) -> Mdf { + Mdf::from_ol((self.yof() & OL_MASK) >> 3, self.year_flags()) + } + + /// Makes a new `NaiveDate` with the packed month-day-flags changed. + /// + /// Returns `None` when the resulting `NaiveDate` would be invalid. + #[inline] + const fn with_mdf(&self, mdf: Mdf) -> Option { + debug_assert!(self.year_flags().0 == mdf.year_flags().0); + match mdf.ordinal() { + Some(ordinal) => { + Some(NaiveDate::from_yof((self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32)) + } + None => None, // Non-existing date + } + } + + /// Makes a new `NaiveDate` for the next calendar date. + /// + /// # Panics + /// + /// Panics when `self` is the last representable date. + #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] + #[inline] + #[must_use] + pub const fn succ(&self) -> NaiveDate { + expect(self.succ_opt(), "out of bound") + } + + /// Makes a new `NaiveDate` for the next calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the last representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MAX.succ_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn succ_opt(&self) -> Option { + let new_ol = (self.yof() & OL_MASK) + (1 << 4); + match new_ol <= MAX_OL { + true => Some(NaiveDate::from_yof(self.yof() & !OL_MASK | new_ol)), + false => NaiveDate::from_yo_opt(self.year() + 1, 1), + } + } + + /// Makes a new `NaiveDate` for the previous calendar date. + /// + /// # Panics + /// + /// Panics when `self` is the first representable date. + #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] + #[inline] + #[must_use] + pub const fn pred(&self) -> NaiveDate { + expect(self.pred_opt(), "out of bound") + } + + /// Makes a new `NaiveDate` for the previous calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the first representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MIN.pred_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn pred_opt(&self) -> Option { + let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - (1 << 4); + match new_shifted_ordinal > 0 { + true => Some(NaiveDate::from_yof(self.yof() & !ORDINAL_MASK | new_shifted_ordinal)), + false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), + } + } + + /// Adds the number of whole days in the given `TimeDelta` to the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + #[must_use] + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { + let days = rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) + } + + /// Subtracts the number of whole days in the given `TimeDelta` from the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + #[must_use] + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { + let days = -rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) + } + + /// Subtracts another `NaiveDate` from the current date. + /// Returns a `TimeDelta` of integral numbers. + /// + /// This does not overflow or underflow at all, + /// as all possible output fits in the range of `TimeDelta`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let since = NaiveDate::signed_duration_since; + /// + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), + /// TimeDelta::try_days(1).unwrap() + /// ); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::try_days(-1).unwrap()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), + /// TimeDelta::try_days(100).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), + /// TimeDelta::try_days(365).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), + /// TimeDelta::try_days(365 * 4 + 1).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), + /// TimeDelta::try_days(365 * 400 + 97).unwrap() + /// ); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: Self) -> TimeDelta { + let year1 = self.year(); + let year2 = rhs.year(); + let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); + let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); + let cycle1 = yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; + let days = (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2); + // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 + // years. + expect(TimeDelta::try_days(days), "always in range") + } + + /// Returns the absolute difference between two `NaiveDate`s measured as the number of days. + /// + /// This is always an integer, non-negative number, similar to `abs_diff` in `std`. + /// + /// # Example + /// + /// ``` + /// # use chrono::{Days, NaiveDate}; + /// # + /// let date1: NaiveDate = "2020-01-01".parse().unwrap(); + /// let date2: NaiveDate = "2020-01-31".parse().unwrap(); + /// assert_eq!(date2.abs_diff(date1), Days::new(30)); + /// assert_eq!(date1.abs_diff(date2), Days::new(30)); + /// ``` + pub const fn abs_diff(self, rhs: Self) -> Days { + Days::new(i32::abs_diff(self.num_days_from_ce(), rhs.num_days_from_ce()) as u64) + } + + /// Returns the number of whole years from the given `base` until `self`. + /// + /// # Errors + /// + /// Returns `None` if `base > self`. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate}; + /// # + /// let base: NaiveDate = "2025-01-01".parse().unwrap(); + /// let date: NaiveDate = "2030-01-01".parse().unwrap(); + /// + /// assert_eq!(date.years_since(base), Some(5)) + /// ``` + #[must_use] + pub const fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + // Comparing tuples is not (yet) possible in const context. Instead we combine month and + // day into one `u32` for easy comparison. + if ((self.month() << 5) | self.day()) < ((base.month() << 5) | base.day()) { + years -= 1; + } + + match years >= 0 { + true => Some(years as u32), + false => None, + } + } + + /// Formats the date with the specified formatting items. + /// Otherwise it is the same as the ordinary `format` method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; + /// + /// let fmt = StrftimeItems::new("%Y-%m-%d"); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); + /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(Some(*self), None, items) + } + + /// Formats the date with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// # Panics + /// + /// Converting or formatting the returned `DelayedFormat` panics if the format string is wrong. + /// Because of this delayed failure, you are recommended to immediately use the `DelayedFormat` + /// value. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); + /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Formats the date with the specified formatting items and locale. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new_with_locale(Some(*self), None, items, locale) + } + + /// Formats the date with the specified format string and locale. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } + + /// Returns an iterator that steps by days across all representable dates. + /// + /// # Example + /// + /// ``` + /// # use chrono::NaiveDate; + /// + /// let expected = [ + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 28).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 29).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 1).unwrap(), + /// ]; + /// + /// let mut count = 0; + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).enumerate() { + /// assert_eq!(d, expected[idx]); + /// count += 1; + /// } + /// assert_eq!(count, 4); + /// + /// // The iterator is double-ended: reversing a bounded range yields the same + /// // dates in reverse order. + /// for d in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).rev() { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } + /// ``` + #[inline] + pub const fn iter_days(&self) -> NaiveDateDaysIterator { + NaiveDateDaysIterator { value: *self, end: NaiveDate::MAX } + } + + /// Returns an iterator that steps by weeks across all representable dates. + /// + /// # Example + /// + /// ``` + /// # use chrono::NaiveDate; + /// + /// let expected = [ + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 5).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 12).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 19).unwrap(), + /// ]; + /// + /// let mut count = 0; + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).enumerate() { + /// assert_eq!(d, expected[idx]); + /// count += 1; + /// } + /// assert_eq!(count, 4); + /// + /// // The iterator is double-ended: reversing a bounded range yields the same + /// // dates in reverse order. + /// for d in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).rev() { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } + /// ``` + #[inline] + pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator { + // Align the exclusive upper bound to the weekly grid starting at `*self`, so + // that `next_back` yields the same weeks as `next`, only in reverse order. + let weeks = NaiveDate::MAX.signed_duration_since(*self).num_weeks(); + // `weeks * 7` days is at most `NaiveDate::MAX - *self`, so this never overflows. + let end = match self.checked_add_days(Days::new((weeks * 7) as u64)) { + Some(end) => end, + None => *self, + }; + NaiveDateWeeksIterator { value: *self, end } + } + + /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] + /// specified. + #[inline] + pub const fn week(&self, start: Weekday) -> NaiveWeek { + NaiveWeek::new(*self, start) + } + + /// Returns `true` if this is a leap year. + /// + /// ``` + /// # use chrono::NaiveDate; + /// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); + /// ``` + pub const fn leap_year(&self) -> bool { + self.yof() & (0b1000) == 0 + } + + // This duplicates `Datelike::year()`, because trait methods can't be const yet. + #[inline] + const fn year(&self) -> i32 { + self.yof() >> 13 + } + + /// Returns the day of year starting from 1. + // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. + #[inline] + const fn ordinal(&self) -> u32 { + ((self.yof() & ORDINAL_MASK) >> 4) as u32 + } + + // This duplicates `Datelike::month()`, because trait methods can't be const yet. + #[inline] + const fn month(&self) -> u32 { + self.mdf().month() + } + + // This duplicates `Datelike::day()`, because trait methods can't be const yet. + #[inline] + const fn day(&self) -> u32 { + self.mdf().day() + } + + /// Returns the day of week. + // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. + #[inline] + pub(super) const fn weekday(&self) -> Weekday { + match (((self.yof() & ORDINAL_MASK) >> 4) + (self.yof() & WEEKDAY_FLAGS_MASK)) % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } + } + + #[inline] + const fn year_flags(&self) -> YearFlags { + YearFlags((self.yof() & YEAR_FLAGS_MASK) as u8) + } + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. + pub(crate) const fn num_days_from_ce(&self) -> i32 { + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1970 as day 0. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ymd(1, 1, 1).to_epoch_days(), -719162); + /// assert_eq!(from_ymd(1970, 1, 1).to_epoch_days(), 0); + /// assert_eq!(from_ymd(2005, 9, 10).to_epoch_days(), 13036); + /// ``` + pub const fn to_epoch_days(&self) -> i32 { + self.num_days_from_ce() - UNIX_EPOCH_DAY as i32 + } + + /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. + /// + /// In a valid value an ordinal is never `0`, and neither are the year flags. This method + /// doesn't do any validation in release builds. + #[inline] + const fn from_yof(yof: i32) -> NaiveDate { + // The following are the invariants our ordinal and flags should uphold for a valid + // `NaiveDate`. + debug_assert!(((yof & OL_MASK) >> 3) > 1); + debug_assert!(((yof & OL_MASK) >> 3) <= MAX_OL); + debug_assert!((yof & 0b111) != 000); + NaiveDate { yof: unsafe { NonZeroI32::new_unchecked(yof) } } + } + + /// Get the raw year-ordinal-flags `i32`. + #[inline] + const fn yof(&self) -> i32 { + self.yof.get() + } + + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). + pub const MIN: NaiveDate = NaiveDate::from_yof((MIN_YEAR << 13) | (1 << 4) | 0o12 /* D */); + /// The maximum possible `NaiveDate` (December 31, 262142 CE). + pub const MAX: NaiveDate = + NaiveDate::from_yof((MAX_YEAR << 13) | (365 << 4) | 0o16 /* G */); + + /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). + pub(crate) const BEFORE_MIN: NaiveDate = + NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /* FE */); + /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). + pub(crate) const AFTER_MAX: NaiveDate = + NaiveDate::from_yof(((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /* F */); +} + +impl Datelike for NaiveDate { + /// Returns the year number in the [calendar date](#calendar-date). + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE + /// ``` + #[inline] + fn year(&self) -> i32 { + self.year() + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); + /// ``` + #[inline] + fn month(&self) -> u32 { + self.month() + } + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); + /// ``` + #[inline] + fn month0(&self) -> u32 { + self.month() - 1 + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular month. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_month(year: i32, month: u32) -> u32 { + /// // the first day of the next month... + /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; + /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original month + /// d.pred_opt().unwrap().day() + /// } + /// + /// assert_eq!(ndays_in_month(2015, 8), 31); + /// assert_eq!(ndays_in_month(2015, 9), 30); + /// assert_eq!(ndays_in_month(2015, 12), 31); + /// assert_eq!(ndays_in_month(2016, 2), 29); + /// assert_eq!(ndays_in_month(2017, 2), 28); + /// ``` + #[inline] + fn day(&self) -> u32 { + self.day() + } + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); + /// ``` + #[inline] + fn day0(&self) -> u32 { + self.mdf().day() - 1 + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal(), 251); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular year. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_year(year: i32) -> u32 { + /// // the first day of the next year... + /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original year + /// d.pred_opt().unwrap().ordinal() + /// } + /// + /// assert_eq!(ndays_in_year(2015), 365); + /// assert_eq!(ndays_in_year(2016), 366); + /// assert_eq!(ndays_in_year(2017), 365); + /// assert_eq!(ndays_in_year(2000), 366); + /// assert_eq!(ndays_in_year(2100), 365); + /// ``` + #[inline] + fn ordinal(&self) -> u32 { + ((self.yof() & ORDINAL_MASK) >> 4) as u32 + } + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal0(), 250); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal0(), 73); + /// ``` + #[inline] + fn ordinal0(&self) -> u32 { + self.ordinal() - 1 + } + + /// Returns the day of week. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); + /// ``` + #[inline] + fn weekday(&self) -> Weekday { + self.weekday() + } + + #[inline] + fn iso_week(&self) -> IsoWeek { + IsoWeek::from_yof(self.year(), self.ordinal(), self.year_flags()) + } + + /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. + /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 8).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 8).unwrap()) + /// ); + /// ``` + /// + /// A leap day (February 29) is a case where this method can return `None`. + /// + /// ``` + /// # use chrono::{NaiveDate, Datelike}; + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2015).is_none()); + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2020).is_some()); + /// ``` + /// + /// Don't use `with_year` if you want the ordinal date to stay the same: + /// + /// ``` + /// # use chrono::{Datelike, NaiveDate}; + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` + #[inline] + fn with_year(&self, year: i32) -> Option { + // we need to operate with `mdf` since we should keep the month and day number as is + let mdf = self.mdf(); + + // adjust the flags as needed + let flags = YearFlags::from_year(year); + let mdf = mdf.with_flags(flags); + + NaiveDate::from_mdf(year, mdf) + } + + /// Makes a new `NaiveDate` with the month number (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // No month 13 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); // No Feb 30 + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); + /// ``` + #[inline] + fn with_month(&self, month: u32) -> Option { + self.with_mdf(self.mdf().with_month(month)?) + } + + /// Makes a new `NaiveDate` with the month number (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // No month 12 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // No Feb 30 + /// ``` + #[inline] + fn with_month0(&self, month0: u32) -> Option { + let month = month0.checked_add(1)?; + self.with_mdf(self.mdf().with_month(month)?) + } + + /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), None); + /// // no September 31 + /// ``` + #[inline] + fn with_day(&self, day: u32) -> Option { + self.with_mdf(self.mdf().with_day(day)?) + } + + /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), None); + /// // no September 31 + /// ``` + #[inline] + fn with_day0(&self, day0: u32) -> Option { + let day = day0.checked_add(1)?; + self.with_mdf(self.mdf().with_day(day)?) + } + + /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(366), + /// None); // 2015 had only 365 days + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); + /// ``` + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option { + if ordinal == 0 || ordinal > 366 { + return None; + } + let yof = (self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate::from_yof(yof)), + false => None, // Does not exist: Ordinal 366 in a common year. + } + } + + /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(365), + /// None); // 2015 had only 365 days + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); + /// ``` + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option { + let ordinal = ordinal0.checked_add(1)?; + self.with_ordinal(ordinal) + } +} + +/// Add `TimeDelta` to `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(-1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(364).unwrap(), from_ymd(2014, 12, 31)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2018, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(2414, 1, 1) +/// ); +/// ``` +/// +/// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed +impl Add for NaiveDate { + type Output = NaiveDate; + + #[inline] + #[track_caller] + fn add(self, rhs: TimeDelta) -> NaiveDate { + self.checked_add_signed(rhs).expect("`NaiveDate + TimeDelta` overflowed") + } +} + +/// Add-assign of `TimeDelta` to `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of days +/// towards `TimeDelta::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDate { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add `Months` to `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); +/// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); +/// ``` +impl Add for NaiveDate { + type Output = NaiveDate; + + #[track_caller] + fn add(self, months: Months) -> Self::Output { + self.checked_add_months(months).expect("`NaiveDate + Months` out of range") + } +} + +/// Subtract `Months` from `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_sub_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); +/// ``` +impl Sub for NaiveDate { + type Output = NaiveDate; + + #[track_caller] + fn sub(self, months: Months) -> Self::Output { + self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") + } +} + +/// Add `Days` to `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_days` to get an `Option` instead. +impl Add for NaiveDate { + type Output = NaiveDate; + + #[track_caller] + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`NaiveDate + Days` out of range") + } +} + +/// Subtract `Days` from `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_days` to get an `Option` instead. +impl Sub for NaiveDate { + type Output = NaiveDate; + + #[track_caller] + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`NaiveDate - Days` out of range") + } +} + +/// Subtract `TimeDelta` from `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(-1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(364).unwrap(), from_ymd(2013, 1, 2)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2010, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(1614, 1, 1) +/// ); +/// ``` +/// +/// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed +impl Sub for NaiveDate { + type Output = NaiveDate; + + #[inline] + #[track_caller] + fn sub(self, rhs: TimeDelta) -> NaiveDate { + self.checked_sub_signed(rhs).expect("`NaiveDate - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDate { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtracts another `NaiveDate` from the current date. +/// Returns a `TimeDelta` of integral numbers. +/// +/// This does not overflow or underflow at all, +/// as all possible output fits in the range of `TimeDelta`. +/// +/// The implementation is a wrapper around +/// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), TimeDelta::zero()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), TimeDelta::try_days(1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::try_days(-1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::try_days(100).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::try_days(365).unwrap()); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), +/// TimeDelta::try_days(365 * 4 + 1).unwrap() +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), +/// TimeDelta::try_days(365 * 400 + 97).unwrap() +/// ); +/// ``` +impl Sub for NaiveDate { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveDate) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +impl From for NaiveDate { + fn from(naive_datetime: NaiveDateTime) -> Self { + naive_datetime.date() + } +} + +/// Iterator over `NaiveDate` with a step size of one day. +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] +pub struct NaiveDateDaysIterator { + value: NaiveDate, + // Exclusive upper bound. The iterator yields the half-open range `[value, end)`; + // `next` advances `value` from the front and `next_back` lowers `end` from the back. + end: NaiveDate, +} + +impl Iterator for NaiveDateDaysIterator { + type Item = NaiveDate; + + fn next(&mut self) -> Option { + if self.value >= self.end { + return None; + } + let current = self.value; + // `succ_opt()` can't return `None` because `current < end <= NaiveDate::MAX`. + self.value = current.succ_opt()?; + Some(current) + } + + fn size_hint(&self) -> (usize, Option) { + let exact_size = self.end.signed_duration_since(self.value).num_days(); + (exact_size as usize, Some(exact_size as usize)) + } +} + +impl ExactSizeIterator for NaiveDateDaysIterator {} + +impl DoubleEndedIterator for NaiveDateDaysIterator { + fn next_back(&mut self) -> Option { + if self.value >= self.end { + return None; + } + // `pred_opt()` can't return `None` because `end > value >= NaiveDate::MIN`. + self.end = self.end.pred_opt()?; + Some(self.end) + } + + fn nth_back(&mut self, n: usize) -> Option { + // Skipping from the back in O(1) keeps `take(k).rev()` cheap despite the + // iterator spanning up to `NaiveDate::MAX`. + if n >= self.len() { + self.end = self.value; + return None; + } + // The `n`-th element from the back is `end - (n + 1)` days, which stays + // within `[value, end)` and so can't underflow past `NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new(n as u64 + 1))?; + Some(self.end) + } +} + +impl FusedIterator for NaiveDateDaysIterator {} + +/// Iterator over `NaiveDate` with a step size of one week. +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] +pub struct NaiveDateWeeksIterator { + value: NaiveDate, + // Exclusive upper bound, aligned to the weekly grid starting at `value`. The + // iterator yields `[value, end)` in weekly steps; `next` advances `value` from + // the front and `next_back` lowers `end` from the back. + end: NaiveDate, +} + +impl Iterator for NaiveDateWeeksIterator { + type Item = NaiveDate; + + fn next(&mut self) -> Option { + if self.value >= self.end { + return None; + } + let current = self.value; + // Can't overflow because `current + 7 days <= end <= NaiveDate::MAX`. + self.value = current.checked_add_days(Days::new(7))?; + Some(current) + } + + fn size_hint(&self) -> (usize, Option) { + let exact_size = self.end.signed_duration_since(self.value).num_weeks(); + (exact_size as usize, Some(exact_size as usize)) + } +} + +impl ExactSizeIterator for NaiveDateWeeksIterator {} + +impl DoubleEndedIterator for NaiveDateWeeksIterator { + fn next_back(&mut self) -> Option { + if self.value >= self.end { + return None; + } + // Can't underflow because `end - 7 days >= value >= NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new(7))?; + Some(self.end) + } + + fn nth_back(&mut self, n: usize) -> Option { + // Skipping from the back in O(1) keeps `take(k).rev()` cheap despite the + // iterator spanning up to `NaiveDate::MAX`. + if n >= self.len() { + self.end = self.value; + return None; + } + // The `n`-th element from the back is `end - 7 * (n + 1)` days, which stays + // within `[value, end)` and so can't underflow past `NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new((n as u64 + 1) * 7))?; + Some(self.end) + } +} + +impl FusedIterator for NaiveDateWeeksIterator {} + +/// The `Debug` output of the naive date `d` is the same as +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); +/// ``` +/// +/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. +/// +/// ``` +/// # use chrono::NaiveDate; +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); +/// ``` +impl fmt::Debug for NaiveDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use core::fmt::Write; + + let year = self.year(); + let mdf = self.mdf(); + if (0..=9999).contains(&year) { + write_hundreds(f, (year / 100) as u8)?; + write_hundreds(f, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(f, "{year:+05}")?; + } + + f.write_char('-')?; + write_hundreds(f, mdf.month() as u8)?; + f.write_char('-')?; + write_hundreds(f, mdf.day() as u8) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveDate { + fn format(&self, fmt: defmt::Formatter) { + let year = self.year(); + let mdf = self.mdf(); + if (0..=9999).contains(&year) { + defmt::write!(fmt, "{:02}{:02}", year / 100, year % 100); + } else { + // ISO 8601 requires the explicit sign for out-of-range years + let sign = ['+', '-'][(year < 0) as usize]; + defmt::write!(fmt, "{}{:05}", sign, year.abs()); + } + + defmt::write!(fmt, "-{:02}-{:02}", mdf.month(), mdf.day()); + } +} + +/// The `Display` output of the naive date `d` is the same as +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); +/// ``` +/// +/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. +/// +/// ``` +/// # use chrono::NaiveDate; +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); +/// ``` +impl fmt::Display for NaiveDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Parsing a `str` into a `NaiveDate` uses the same format, +/// [`%Y-%m-%d`](crate::format::strftime), as in `Debug` and `Display`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap(); +/// assert_eq!("2015-09-18".parse::(), Ok(d)); +/// +/// let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap(); +/// assert_eq!("+12345-6-7".parse::(), Ok(d)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveDate { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), + ]; + + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_naive_date() + } +} + +/// The default value for a NaiveDate is 1st of January 1970. +/// +/// # Example +/// +/// ```rust +/// use chrono::NaiveDate; +/// +/// let default_date = NaiveDate::default(); +/// assert_eq!(default_date, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); +/// ``` +impl Default for NaiveDate { + fn default() -> Self { + NaiveDate::from_ymd_opt(1970, 1, 1).unwrap() + } +} + +const fn cycle_to_yo(cycle: u32) -> (u32, u32) { + let mut year_mod_400 = cycle / 365; + let mut ordinal0 = cycle % 365; + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; + if ordinal0 < delta { + year_mod_400 -= 1; + ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; + } else { + ordinal0 -= delta; + } + (year_mod_400, ordinal0 + 1) +} + +const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { + year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 +} + +const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { + (val.div_euclid(div), val.rem_euclid(div)) +} + +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; + +const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + +const LEAP_YEAR_MASK: i32 = 0b1000; + +// OL: ordinal and leap year flag. +// With only these parts of the date an ordinal 366 in a common year would be encoded as +// `((366 << 1) | 1) << 3`, and in a leap year as `((366 << 1) | 0) << 3`, which is less. +// This allows for efficiently checking the ordinal exists depending on whether this is a leap year. +const OL_MASK: i32 = ORDINAL_MASK | LEAP_YEAR_MASK; +const MAX_OL: i32 = 366 << 4; + +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const WEEKDAY_FLAGS_MASK: i32 = 0b111; + +const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; + +const YEAR_DELTAS: &[u8; 401] = &[ + 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, + 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, + 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 + 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, + 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, + 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, + 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, + 48, 49, 49, 49, // 200 + 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, + 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, + 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, + 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, + 72, 73, 73, 73, // 300 + 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, + 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, + 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, + 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, + 96, 97, 97, 97, 97, // 400+1 +]; + +#[cfg(feature = "serde")] +mod serde { + use super::NaiveDate; + use core::fmt; + use serde::{de, ser}; + + // TODO not very optimized for space (binary formats would want something better) + + impl ser::Serialize for NaiveDate { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl fmt::Display for FormatWrapped<'_, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + serializer.collect_str(&FormatWrapped { inner: &self }) + } + } + + struct NaiveDateVisitor; + + impl de::Visitor<'_> for NaiveDateVisitor { + type Value = NaiveDate; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } + } + + impl<'de> de::Deserialize<'de> for NaiveDate { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveDateVisitor) + } + } + + #[cfg(test)] + mod tests { + use crate::NaiveDate; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(), + Some(r#""2014-07-24""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(), + Some(r#""0000-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), + Some(r#""-0001-12-31""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN).ok(), + Some(r#""-262143-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MAX).ok(), + Some(r#""+262142-12-31""#.into()) + ); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!( + from_str(r#""2016-7-8""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8)); + assert_eq!( + from_str(r#""0000-01-01""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""0-1-1""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""-0001-12-31""#).ok(), + Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) + ); + assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""20001231""#).is_err()); + assert!(from_str(r#""2000-00-00""#).is_err()); + assert!(from_str(r#""2000-02-30""#).is_err()); + assert!(from_str(r#""2001-02-29""#).is_err()); + assert!(from_str(r#""2002-002-28""#).is_err()); + assert!(from_str(r#""yyyy-mm-dd""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"20.01"#).is_err()); + let min = i32::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i32::MAX.to_string(); + assert!(from_str(&max).is_err()); + let min = i64::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i64::MAX.to_string(); + assert!(from_str(&max).is_err()); + assert!(from_str(r#"{}"#).is_err()); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap(); + let encoded = serialize(&d).unwrap(); + let decoded: NaiveDate = deserialize(&encoded).unwrap(); + assert_eq!(d, decoded); + } + } +} diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs new file mode 100644 index 0000000000..ba948b586c --- /dev/null +++ b/src/naive/date/tests.rs @@ -0,0 +1,945 @@ +use super::{Days, MAX_YEAR, MIN_YEAR, Months, NaiveDate}; +use crate::naive::internals::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; +use crate::{Datelike, TimeDelta, Weekday}; + +// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, +// we use a separate run-time test. +#[test] +fn test_date_bounds() { + let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap(); + let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); + assert!( + NaiveDate::MIN == calculated_min, + "`NaiveDate::MIN` should have year flag {:?}", + calculated_min.year_flags() + ); + assert!( + NaiveDate::MAX == calculated_max, + "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", + calculated_max.year_flags(), + calculated_max.ordinal() + ); + + // let's also check that the entire range do not exceed 2^44 seconds + // (sometimes used for bounding `TimeDelta` against overflow) + let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); + let maxsecs = maxsecs + 86401; // also take care of DateTime + assert!( + maxsecs < (1 << MAX_BITS), + "The entire `NaiveDate` range somehow exceeds 2^{MAX_BITS} seconds" + ); + + const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; + assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); + + const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; + assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); +} + +#[test] +fn diff_months() { + // identity + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), + Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) + ); + + // add with months exceeding `i32::MAX` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_add_months(Months::new(i32::MAX as u32 + 1)), + None + ); + + // sub with months exceeding `i32::MIN` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), + None + ); + + // add overflowing year + assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); + + // add underflowing year + assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); + + // sub crossing year 0 boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), + Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) + ); + + // add crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), + Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) + ); + + // sub crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) + ); + + // add clamping day, non-leap year + assert_eq!( + NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), + Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) + ); + + // add to leap day + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), + Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) + ); + + // add into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), + Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) + ); + + // sub into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) + ); + + // add into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), + Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) + ); + + // sub into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), + Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) + ); +} + +#[test] +fn test_readme_doomsday() { + for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { + // even months + let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); + let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); + let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); + let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); + let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); + + // nine to five, seven-eleven + let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); + let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); + let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); + let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); + + // "March 0" + let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); + + let weekday = d30.weekday(); + let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; + assert!(other_dates.iter().all(|d| d.weekday() == weekday)); + } +} + +#[test] +fn test_date_from_ymd() { + let from_ymd = NaiveDate::from_ymd_opt; + + assert!(from_ymd(2012, 0, 1).is_none()); + assert!(from_ymd(2012, 1, 1).is_some()); + assert!(from_ymd(2012, 2, 29).is_some()); + assert!(from_ymd(2014, 2, 29).is_none()); + assert!(from_ymd(2014, 3, 0).is_none()); + assert!(from_ymd(2014, 3, 1).is_some()); + assert!(from_ymd(2014, 3, 31).is_some()); + assert!(from_ymd(2014, 3, 32).is_none()); + assert!(from_ymd(2014, 12, 31).is_some()); + assert!(from_ymd(2014, 13, 1).is_none()); +} + +#[test] +fn test_date_from_yo() { + let from_yo = NaiveDate::from_yo_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(from_yo(2012, 0), None); + assert_eq!(from_yo(2012, 1), Some(ymd(2012, 1, 1))); + assert_eq!(from_yo(2012, 2), Some(ymd(2012, 1, 2))); + assert_eq!(from_yo(2012, 32), Some(ymd(2012, 2, 1))); + assert_eq!(from_yo(2012, 60), Some(ymd(2012, 2, 29))); + assert_eq!(from_yo(2012, 61), Some(ymd(2012, 3, 1))); + assert_eq!(from_yo(2012, 100), Some(ymd(2012, 4, 9))); + assert_eq!(from_yo(2012, 200), Some(ymd(2012, 7, 18))); + assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26))); + assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31))); + assert_eq!(from_yo(2012, 367), None); + assert_eq!(from_yo(2012, (1 << 28) | 60), None); + + assert_eq!(from_yo(2014, 0), None); + assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1))); + assert_eq!(from_yo(2014, 2), Some(ymd(2014, 1, 2))); + assert_eq!(from_yo(2014, 32), Some(ymd(2014, 2, 1))); + assert_eq!(from_yo(2014, 59), Some(ymd(2014, 2, 28))); + assert_eq!(from_yo(2014, 60), Some(ymd(2014, 3, 1))); + assert_eq!(from_yo(2014, 100), Some(ymd(2014, 4, 10))); + assert_eq!(from_yo(2014, 200), Some(ymd(2014, 7, 19))); + assert_eq!(from_yo(2014, 300), Some(ymd(2014, 10, 27))); + assert_eq!(from_yo(2014, 365), Some(ymd(2014, 12, 31))); + assert_eq!(from_yo(2014, 366), None); +} + +#[test] +fn test_date_from_isoywd() { + let from_isoywd = NaiveDate::from_isoywd_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(from_isoywd(2004, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); + assert_eq!(from_isoywd(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); + assert_eq!(from_isoywd(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); + assert_eq!(from_isoywd(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); + assert_eq!(from_isoywd(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); + assert_eq!(from_isoywd(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); + assert_eq!(from_isoywd(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); + assert_eq!(from_isoywd(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); + assert_eq!(from_isoywd(2004, 54, Weekday::Mon), None); + + assert_eq!(from_isoywd(2011, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); + assert_eq!(from_isoywd(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); + assert_eq!(from_isoywd(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); + assert_eq!(from_isoywd(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); + + assert_eq!(from_isoywd(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); + assert_eq!(from_isoywd(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); + assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); + assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); + assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None); + + // Extreme years are out of range for `NaiveDate` and must return `None`, not + // panic (the internal `year - 1` / `year + 1` step used to overflow here). + assert_eq!(from_isoywd(i32::MIN, 1, Weekday::Mon), None); + assert_eq!(from_isoywd(i32::MAX, 1, Weekday::Mon), None); +} + +#[test] +fn test_date_from_isoywd_and_iso_week() { + for year in 2000..2401 { + for week in 1..54 { + for &weekday in [ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] + .iter() + { + let d = NaiveDate::from_isoywd_opt(year, week, weekday); + if let Some(d) = d { + assert_eq!(d.weekday(), weekday); + let w = d.iso_week(); + assert_eq!(w.year(), year); + assert_eq!(w.week(), week); + } + } + } + } + + for year in 2000..2401 { + for month in 1..13 { + for day in 1..32 { + let d = NaiveDate::from_ymd_opt(year, month, day); + if let Some(d) = d { + let w = d.iso_week(); + let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); + assert_eq!(d, d_.unwrap()); + } + } + } + } +} + +#[test] +fn test_date_from_num_days_from_ce() { + let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; + assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); + assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); + assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); + assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); + assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); + assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap())); + assert_eq!( + from_ndays_from_ce(146097 * 5 + 1), + Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) + ); + assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE + assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE + + for days in (-9999..10001).map(|x| x * 100) { + assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); + } + + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); + + assert_eq!(from_ndays_from_ce(i32::MIN), None); + assert_eq!(from_ndays_from_ce(i32::MAX), None); +} + +#[test] +fn test_date_from_epoch_days() { + let from_epoch_days = NaiveDate::from_epoch_days; + assert_eq!(from_epoch_days(-719_162), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_epoch_days(0), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_epoch_days(1), Some(NaiveDate::from_ymd_opt(1970, 1, 2).unwrap())); + assert_eq!(from_epoch_days(2), Some(NaiveDate::from_ymd_opt(1970, 1, 3).unwrap())); + assert_eq!(from_epoch_days(30), Some(NaiveDate::from_ymd_opt(1970, 1, 31).unwrap())); + assert_eq!(from_epoch_days(31), Some(NaiveDate::from_ymd_opt(1970, 2, 1).unwrap())); + assert_eq!(from_epoch_days(58), Some(NaiveDate::from_ymd_opt(1970, 2, 28).unwrap())); + assert_eq!(from_epoch_days(59), Some(NaiveDate::from_ymd_opt(1970, 3, 1).unwrap())); + assert_eq!(from_epoch_days(364), Some(NaiveDate::from_ymd_opt(1970, 12, 31).unwrap())); + assert_eq!(from_epoch_days(365), Some(NaiveDate::from_ymd_opt(1971, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 2), Some(NaiveDate::from_ymd_opt(1972, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(1973, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 4 + 1), Some(NaiveDate::from_ymd_opt(1974, 1, 1).unwrap())); + assert_eq!(from_epoch_days(13036), Some(NaiveDate::from_ymd_opt(2005, 9, 10).unwrap())); + assert_eq!(from_epoch_days(-365), Some(NaiveDate::from_ymd_opt(1969, 1, 1).unwrap())); + assert_eq!(from_epoch_days(-366), Some(NaiveDate::from_ymd_opt(1968, 12, 31).unwrap())); + + for days in (-9999..10001).map(|x| x * 100) { + assert_eq!(from_epoch_days(days).map(|d| d.to_epoch_days()), Some(days)); + } + + assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days()), Some(NaiveDate::MIN)); + assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days() - 1), None); + assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days()), Some(NaiveDate::MAX)); + assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days() + 1), None); + + assert_eq!(from_epoch_days(i32::MIN), None); + assert_eq!(from_epoch_days(i32::MAX), None); +} + +#[test] +fn test_date_from_weekday_of_month_opt() { + let ymwd = NaiveDate::from_weekday_of_month_opt; + assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); +} + +#[test] +fn test_date_fields() { + fn check(year: i32, month: u32, day: u32, ordinal: u32) { + let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); + assert_eq!(d1.year(), year); + assert_eq!(d1.month(), month); + assert_eq!(d1.day(), day); + assert_eq!(d1.ordinal(), ordinal); + + let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(d2.year(), year); + assert_eq!(d2.month(), month); + assert_eq!(d2.day(), day); + assert_eq!(d2.ordinal(), ordinal); + + assert_eq!(d1, d2); + } + + check(2012, 1, 1, 1); + check(2012, 1, 2, 2); + check(2012, 2, 1, 32); + check(2012, 2, 29, 60); + check(2012, 3, 1, 61); + check(2012, 4, 9, 100); + check(2012, 7, 18, 200); + check(2012, 10, 26, 300); + check(2012, 12, 31, 366); + + check(2014, 1, 1, 1); + check(2014, 1, 2, 2); + check(2014, 2, 1, 32); + check(2014, 2, 28, 59); + check(2014, 3, 1, 60); + check(2014, 4, 10, 100); + check(2014, 7, 19, 200); + check(2014, 10, 27, 300); + check(2014, 12, 31, 365); +} + +#[test] +fn test_date_weekday() { + assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); + // May 20, 1875 = ISO 8601 reference date + assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); + assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); +} + +#[test] +fn test_date_with_fields() { + let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); + assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); + assert_eq!(d.with_year(-100), None); + assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); + assert_eq!(d.with_year(1900), None); + assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_year(2001), None); + assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); + assert_eq!(d.with_year(i32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); + assert_eq!(d.with_month(0), None); + assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); + assert_eq!(d.with_month(2), None); + assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); + assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); + assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); + assert_eq!(d.with_month(13), None); + assert_eq!(d.with_month(u32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); + assert_eq!(d.with_day(0), None); + assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); + assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_day(30), None); + assert_eq!(d.with_day(u32::MAX), None); +} + +#[test] +fn test_date_with_ordinal() { + let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(0), None); + assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); + assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); + assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); + assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal((1 << 28) | 60), None); + let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(366), None); + assert_eq!(d.with_ordinal(u32::MAX), None); +} + +#[test] +fn test_date_num_days_from_ce() { + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + + for year in -9999..10001 { + assert_eq!( + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 + ); + } +} + +#[test] +fn test_date_to_epoch_days() { + assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().to_epoch_days(), 0); + + for year in -9999..10001 { + assert_eq!( + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().to_epoch_days(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().to_epoch_days() + 1 + ); + } +} + +#[test] +fn test_date_succ() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); + assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); + assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); + assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); +} + +#[test] +fn test_date_pred() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); + assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); + assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); + assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); +} + +#[test] +fn test_date_checked_add_signed() { + fn check(lhs: Option, delta: TimeDelta, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_signed(delta), rhs); + assert_eq!(lhs.unwrap().checked_sub_signed(-delta), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), TimeDelta::zero(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), ymd(2014, 1, 1)); + // always round towards zero + check(ymd(2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap(), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), TimeDelta::try_days(-1).unwrap(), ymd(2013, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap(), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), ymd(2414, 1, 1)); + + check(ymd(-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), ymd(5, 1, 1)); + + // overflow check + check( + ymd(0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + ymd(MAX_YEAR, 12, 31), + ); + check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::MAX, None); + check( + ymd(0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + ymd(MIN_YEAR, 1, 1), + ); + check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::MIN, None); +} + +#[test] +fn test_date_signed_duration_since() { + fn check(lhs: Option, rhs: Option, delta: TimeDelta) { + assert_eq!(lhs.unwrap().signed_duration_since(rhs.unwrap()), delta); + assert_eq!(rhs.unwrap().signed_duration_since(lhs.unwrap()), -delta); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), ymd(2014, 1, 1), TimeDelta::zero()); + check(ymd(2014, 1, 2), ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap()); + check(ymd(2014, 12, 31), ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap()); + check(ymd(2015, 1, 3), ymd(2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap()); + check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); + check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); + + check( + ymd(MAX_YEAR, 12, 31), + ymd(0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); + check( + ymd(MIN_YEAR, 1, 1), + ymd(0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); +} + +#[test] +fn test_date_add_days() { + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_days(days), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); + // always round towards zero + check(ymd(2014, 1, 1), Days::new(1), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), Days::new(364), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), Days::new(365 * 4 + 1), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), Days::new(365 * 400 + 97), ymd(2414, 1, 1)); + + check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1)); + + // overflow check + check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31)); + check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); +} + +#[test] +fn test_date_sub_days() { + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_sub_days(days), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); + check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1)); + check(ymd(2014, 12, 31), Days::new(364), ymd(2014, 1, 1)); + check(ymd(2015, 1, 3), Days::new(365 + 2), ymd(2014, 1, 1)); + check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1)); + check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1)); + + check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1)); + check( + ymd(0, 1, 1), + Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()), + ymd(MIN_YEAR, 1, 1), + ); +} + +#[test] +fn test_date_addassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 1); + date += TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymd(2016, 10, 11)); + date += TimeDelta::try_days(30).unwrap(); + assert_eq!(date, ymd(2016, 11, 10)); +} + +#[test] +fn test_date_subassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 11); + date -= TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymd(2016, 10, 1)); + date -= TimeDelta::try_days(2).unwrap(); + assert_eq!(date, ymd(2016, 9, 29)); +} + +#[test] +fn test_date_fmt() { + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); + + assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); + assert_eq!(format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07"); +} + +#[test] +fn test_date_from_str() { + // valid cases + let valid = [ + "-0000000123456-1-2", + " -123456 - 1 - 2 ", + "-12345-1-2", + "-1234-12-31", + "-7-6-5", + "350-2-28", + "360-02-29", + "0360-02-29", + "2015-2 -18", + "2015-02-18", + "+70-2-18", + "+70000-2-18", + "+00007-2-18", + ]; + for &s in &valid { + eprintln!("test_date_from_str valid {s:?}"); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{s}` has failed: {e}"), + }; + eprintln!("d {d:?} (NaiveDate)"); + let s_ = format!("{d:?}"); + eprintln!("s_ {s_:?}"); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") + } + }; + eprintln!("d_ {d_:?} (NaiveDate)"); + assert!( + d == d_, + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {s:?}"); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_date_parse_from_str() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!( + NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymd(2014, 5, 7)) + ); // ignore time and offset + assert_eq!( + NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"), + Ok(ymd(2015, 2, 2)) + ); + assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9))); + assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); + assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); + assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + + assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter + + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); +} + +#[test] +fn test_day_iterator_limit() { + // Forward iteration stops at `NaiveDate::MAX`. + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2); + // Reversing that bounded range yields the same dates, so it is likewise + // limited to 2 elements (previously `rev` walked below the start instead). + assert_eq!( + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).rev().count(), + 2 + ); +} + +#[test] +fn test_week_iterator_limit() { + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).rev().count(), + 2 + ); +} + +#[test] +fn test_iterator_reverse_matches_forward() { + // Regression test for #1757: reversing a bounded window of the day and week + // iterators must yield the same dates as forward iteration, in reverse order. + let start = NaiveDate::from_ymd_opt(2025, 10, 10).unwrap(); + + let mut days_forward: Vec<_> = start.iter_days().take(10).collect(); + let days_reversed: Vec<_> = start.iter_days().take(10).rev().collect(); + days_forward.reverse(); + assert_eq!(days_reversed, days_forward); + + let mut weeks_forward: Vec<_> = start.iter_weeks().take(10).collect(); + let weeks_reversed: Vec<_> = start.iter_weeks().take(10).rev().collect(); + weeks_forward.reverse(); + assert_eq!(weeks_reversed, weeks_forward); +} + +#[test] +fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!( + [52, 53].contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)), + ); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } +} + +#[test] +fn test_with_0_overflow() { + let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap(); + assert!(dt.with_month0(4294967295).is_none()); + assert!(dt.with_day0(4294967295).is_none()); + assert!(dt.with_ordinal0(4294967295).is_none()); +} + +#[test] +fn test_leap_year() { + for year in 0..=MAX_YEAR { + let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + assert_eq!(date.leap_year(), is_leap); + assert_eq!(date.leap_year(), date.with_ordinal(366).is_some()); + } +} + +#[test] +fn test_date_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); + } +} + +#[test] +fn test_weekday_with_yearflags() { + for (year, year_flags, first_weekday) in YEAR_FLAGS { + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); + dbg!(year); + assert_eq!(first_day_of_year.year_flags(), year_flags); + assert_eq!(first_day_of_year.weekday(), first_weekday); + + let mut prev = first_day_of_year.weekday(); + for ordinal in 2u32..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + let expected = prev.succ(); + assert_eq!(date.weekday(), expected); + prev = expected; + } + } +} + +#[test] +fn test_isoweekdate_with_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + // January 4 should be in the first week + let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); + let iso_week = jan4.iso_week(); + assert_eq!(jan4.year_flags(), year_flags); + assert_eq!(iso_week.week(), 1); + } +} + +#[test] +fn test_date_to_mdf_to_date() { + for (year, year_flags, _) in YEAR_FLAGS { + for ordinal in 1..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); + } + } +} + +// Used for testing some methods with all combinations of `YearFlags`. +// (year, flags, first weekday of year) +const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ + (2006, A, Weekday::Sun), + (2005, B, Weekday::Sat), + (2010, C, Weekday::Fri), + (2009, D, Weekday::Thu), + (2003, E, Weekday::Wed), + (2002, F, Weekday::Tue), + (2001, G, Weekday::Mon), + (2012, AG, Weekday::Sun), + (2000, BA, Weekday::Sat), + (2016, CB, Weekday::Fri), + (2004, DC, Weekday::Thu), + (2020, ED, Weekday::Wed), + (2008, FE, Weekday::Tue), + (2024, GF, Weekday::Mon), +]; + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let date_min = NaiveDate::MIN; + let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); + + let date_max = NaiveDate::MAX; + let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); +} + +// MAX_YEAR-12-31 minus 0000-01-01 +// = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) +// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 +// = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR) +const MAX_DAYS_FROM_YEAR_0: i32 = + (MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400; + +// MIN_YEAR-01-01 minus 0000-01-01 +// = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000) +const MIN_DAYS_FROM_YEAR_0: i32 = MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400; + +// only used for testing, but duplicated in naive::datetime +const MAX_BITS: usize = 44; diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs deleted file mode 100644 index 213de89a16..0000000000 --- a/src/naive/datetime.rs +++ /dev/null @@ -1,2534 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 date and time without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use naive::date::{MAX_DATE, MIN_DATE}; -use naive::time::{MAX_TIME, MIN_TIME}; -use naive::{IsoWeek, NaiveDate, NaiveTime}; -use {Datelike, Timelike, Weekday}; - -/// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` -/// will always overflow the addition with any date and time type. -/// -/// So why is this needed? `Duration::seconds(rhs)` may overflow, and we don't have -/// an alternative returning `Option` or `Result`. Thus we need some early bound to avoid -/// touching that call when we are already sure that it WILL overflow... -const MAX_SECS_BITS: usize = 44; - -/// The minimum possible `NaiveDateTime`. -pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime { date: MIN_DATE, time: MIN_TIME }; -/// The maximum possible `NaiveDateTime`. -pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime { date: MAX_DATE, time: MAX_TIME }; - -/// ISO 8601 combined date and time without timezone. -/// -/// # Example -/// -/// `NaiveDateTime` is commonly created from [`NaiveDate`](./struct.NaiveDate.html). -/// -/// ``` -/// use chrono::{NaiveDate, NaiveDateTime}; -/// -/// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); -/// # let _ = dt; -/// ``` -/// -/// You can use typical [date-like](../trait.Datelike.html) and -/// [time-like](../trait.Timelike.html) methods, -/// provided that relevant traits are in the scope. -/// -/// ``` -/// # use chrono::{NaiveDate, NaiveDateTime}; -/// # let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); -/// use chrono::{Datelike, Timelike, Weekday}; -/// -/// assert_eq!(dt.weekday(), Weekday::Fri); -/// assert_eq!(dt.num_seconds_from_midnight(), 33011); -/// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveDateTime { - date: NaiveDate, - time: NaiveTime, -} - -impl NaiveDateTime { - /// Makes a new `NaiveDateTime` from date and time components. - /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) - /// and many other helper constructors on `NaiveDate`. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); - /// - /// let dt = NaiveDateTime::new(d, t); - /// assert_eq!(dt.date(), d); - /// assert_eq!(dt.time(), t); - /// ``` - #[inline] - pub fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { - NaiveDateTime { date: date, time: time } - } - - /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, - /// from the number of non-leap seconds - /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") - /// and the number of nanoseconds since the last whole non-leap second. - /// - /// For a non-naive version of this function see - /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). - /// - /// The nanosecond part can exceed 1,000,000,000 in order to represent the - /// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX - /// timestamp" cannot represent a leap second unambiguously.) - /// - /// Panics on the out-of-range number of seconds and/or invalid nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; - /// - /// let dt = NaiveDateTime::from_timestamp(0, 42_000_000); - /// assert_eq!(dt, NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 0, 42)); - /// - /// let dt = NaiveDateTime::from_timestamp(1_000_000_000, 0); - /// assert_eq!(dt, NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40)); - /// ``` - #[inline] - pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { - let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); - datetime.expect("invalid or out-of-range datetime") - } - - /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, - /// from the number of non-leap seconds - /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") - /// and the number of nanoseconds since the last whole non-leap second. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) - /// - /// Returns `None` on the out-of-range number of seconds and/or invalid nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; - /// use std::i64; - /// - /// let from_timestamp_opt = NaiveDateTime::from_timestamp_opt; - /// - /// assert!(from_timestamp_opt(0, 0).is_some()); - /// assert!(from_timestamp_opt(0, 999_999_999).is_some()); - /// assert!(from_timestamp_opt(0, 1_500_000_000).is_some()); // leap second - /// assert!(from_timestamp_opt(0, 2_000_000_000).is_none()); - /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); - /// ``` - #[inline] - pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { - let (days, secs) = div_mod_floor(secs, 86_400); - let date = days - .to_i32() - .and_then(|days| days.checked_add(719_163)) - .and_then(NaiveDate::from_num_days_from_ce_opt); - let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); - match (date, time) { - (Some(date), Some(time)) => Some(NaiveDateTime { date: date, time: time }), - (_, _) => None, - } - } - - /// Parses a string with the specified format string and returns a new `NaiveDateTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; - /// - /// let parse_from_str = NaiveDateTime::parse_from_str; - /// - /// assert_eq!(parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4))); - /// assert_eq!(parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms_micro(13, 23, 45, 678_900))); - /// ``` - /// - /// Offset is ignored for the purpose of parsing. - /// - /// ``` - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17).and_hms(12, 34, 56))); - /// ``` - /// - /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by - /// treating any time of the form `hh:mm:60` as a leap second. - /// (This equally applies to the formatting, so the round trip is possible.) - /// - /// ``` - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 7, 1).and_hms_milli(8, 59, 59, 1_123))); - /// ``` - /// - /// Missing seconds are assumed to be zero, - /// but out-of-bound times or insufficient fields are errors otherwise. - /// - /// ``` - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), - /// Ok(NaiveDate::from_ymd(1994, 9, 4).and_hms(7, 15, 0))); - /// - /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); - /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); - /// assert!(parse_from_str("94/9/4 17:60", "%y/%m/%d %H:%M").is_err()); - /// assert!(parse_from_str("94/9/4 24:00:00", "%y/%m/%d %H:%M:%S").is_err()); - /// ``` - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// - /// ``` - /// # use chrono::NaiveDateTime; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// let fmt = "%Y-%m-%d %H:%M:%S = UNIX timestamp %s"; - /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); - /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_datetime_with_offset(0) // no offset adjustment - } - - /// Retrieves a date component. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.date(), NaiveDate::from_ymd(2016, 7, 8)); - /// ``` - #[inline] - pub fn date(&self) -> NaiveDate { - self.date - } - - /// Retrieves a time component. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveTime}; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.time(), NaiveTime::from_hms(9, 10, 11)); - /// ``` - #[inline] - pub fn time(&self) -> NaiveTime { - self.time - } - - /// Returns the number of non-leap seconds since the midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 980); - /// assert_eq!(dt.timestamp(), 1); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40); - /// assert_eq!(dt.timestamp(), 1_000_000_000); - /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59); - /// assert_eq!(dt.timestamp(), -1); - /// - /// let dt = NaiveDate::from_ymd(-1, 1, 1).and_hms(0, 0, 0); - /// assert_eq!(dt.timestamp(), -62198755200); - /// ``` - #[inline] - pub fn timestamp(&self) -> i64 { - const UNIX_EPOCH_DAY: i64 = 719_163; - let gregorian_day = i64::from(self.date.num_days_from_ce()); - let seconds_from_midnight = i64::from(self.time.num_seconds_from_midnight()); - (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight - } - - /// Returns the number of non-leap *milliseconds* since midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 Million. (If this is a problem, - /// please file an issue to let me know what domain needs millisecond - /// precision over billions of years, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms_milli(23, 59, 59, 100); - /// assert_eq!(dt.timestamp_millis(), -900); - /// ``` - #[inline] - pub fn timestamp_millis(&self) -> i64 { - let as_ms = self.timestamp() * 1000; - as_ms + i64::from(self.timestamp_subsec_millis()) - } - - /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 Thousand. (If this is a problem, - /// please file an issue to let me know what domain needs microsecond - /// precision over millenia, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_micros(), 1_000_444); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_micros(&self) -> i64 { - let as_us = self.timestamp() * 1_000_000; - as_us + i64::from(self.timestamp_subsec_micros()) - } - - /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// # Panics - /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 years. The dates that can be - /// represented as nanoseconds are between 1677-09-21T00:12:44.0 and - /// 2262-04-11T23:47:16.854775804. - /// - /// (If this is a problem, please file an issue to let me know what domain - /// needs nanosecond precision over millennia, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime}; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); - /// - /// const A_BILLION: i64 = 1_000_000_000; - /// let nanos = dt.timestamp_nanos(); - /// assert_eq!(nanos, 1_000_000_000_000_000_555); - /// assert_eq!( - /// dt, - /// NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - /// ); - /// ``` - #[inline] - pub fn timestamp_nanos(&self) -> i64 { - let as_ns = self.timestamp() * 1_000_000_000; - as_ns + i64::from(self.timestamp_subsec_nanos()) - } - - /// Returns the number of milliseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_millis(), 123); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); - /// ``` - #[inline] - pub fn timestamp_subsec_millis(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000_000 - } - - /// Returns the number of microseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999,999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_micros(), 123_456); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); - /// ``` - #[inline] - pub fn timestamp_subsec_micros(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000 - } - - /// Returns the number of nanoseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999,999,999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); - /// ``` - #[inline] - pub fn timestamp_subsec_nanos(&self) -> u32 { - self.time.nanosecond() - } - - /// Adds given `Duration` to the current date and time. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the addition assumes that **there is no leap second ever**, - /// except when the `NaiveDateTime` itself represents a leap second - /// in which case the assumption becomes that **there is exactly a single leap second ever**. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(-1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(3600 + 60)), - /// Some(hms(4, 6, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7))); - /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); - /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(Duration::milliseconds(450)), - /// Some(hmsm(3, 5, 8, 430))); - /// # } - /// ``` - /// - /// Overflow returns `None`. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); - /// # } - /// ``` - /// - /// Leap seconds are handled, - /// but the addition assumes that it is the only leap second happened. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); - /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_add_signed(Duration::zero()), - /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(-500)), - /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(500)), - /// Some(hmsm(3, 5, 59, 1_800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(800)), - /// Some(hmsm(3, 6, 0, 100))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(10)), - /// Some(hmsm(3, 6, 9, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(-10)), - /// Some(hmsm(3, 5, 50, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); - /// # } - /// ``` - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_add_signed(rhs); - - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) - } - - /// Subtracts given `Duration` from the current date and time. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when the `NaiveDateTime` itself represents a leap second - /// in which case the assumption becomes that **there is exactly a single leap second ever**. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(-1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(3600 + 60)), - /// Some(hms(2, 4, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7))); - /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); - /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(Duration::milliseconds(670)), - /// Some(hmsm(3, 5, 6, 780))); - /// # } - /// ``` - /// - /// Overflow returns `None`. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); - /// # } - /// ``` - /// - /// Leap seconds are handled, - /// but the subtraction assumes that it is the only leap second happened. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); - /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_sub_signed(Duration::zero()), - /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(200)), - /// Some(hmsm(3, 5, 59, 1_100))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(500)), - /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_sub_signed(Duration::seconds(60)), - /// Some(hmsm(3, 5, 0, 300))); - /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); - /// # } - /// ``` - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_sub_signed(rhs); - - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) - } - - /// Subtracts another `NaiveDateTime` from the current date and time. - /// This does not overflow or underflow at all. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when any of the `NaiveDateTime`s themselves represents a leap second - /// in which case the assumption becomes that - /// **there are exactly one (or two) leap second(s) ever**. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// assert_eq!(d.and_hms(3, 5, 7).signed_duration_since(d.and_hms(2, 4, 6)), - /// Duration::seconds(3600 + 60 + 1)); - /// - /// // July 8 is 190th day in the year 2016 - /// let d0 = from_ymd(2016, 1, 1); - /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).signed_duration_since(d0.and_hms(0, 0, 0)), - /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); - /// # } - /// ``` - /// - /// Leap seconds are handled, but the subtraction assumes that - /// there were no other leap seconds happened. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); - /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms(23, 0, 0)), - /// Duration::seconds(3600) + Duration::milliseconds(500)); - /// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0).signed_duration_since(leap), - /// Duration::seconds(3600) - Duration::milliseconds(500)); - /// # } - /// ``` - pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { - self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) - } - - /// Formats the combined date and time with the specified formatting items. - /// Otherwise it is the same as the ordinary [`format`](#method.format) method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(Some(self.date), Some(self.time), items) - } - - /// Formats the combined date and time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveDate; - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); - /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } -} - -impl Datelike for NaiveDateTime { - /// Returns the year number in the [calendar date](./index.html#calendar-date). - /// - /// See also the [`NaiveDate::year`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.year(), 2015); - /// ``` - #[inline] - fn year(&self) -> i32 { - self.date.year() - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - /// - /// See also the [`NaiveDate::month`](./struct.NaiveDate.html#method.month) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.month(), 9); - /// ``` - #[inline] - fn month(&self) -> u32 { - self.date.month() - } - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - /// - /// See also the [`NaiveDate::month0`](./struct.NaiveDate.html#method.month0) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.month0(), 8); - /// ``` - #[inline] - fn month0(&self) -> u32 { - self.date.month0() - } - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - /// - /// See also the [`NaiveDate::day`](./struct.NaiveDate.html#method.day) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.day(), 25); - /// ``` - #[inline] - fn day(&self) -> u32 { - self.date.day() - } - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - /// - /// See also the [`NaiveDate::day0`](./struct.NaiveDate.html#method.day0) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.day0(), 24); - /// ``` - #[inline] - fn day0(&self) -> u32 { - self.date.day0() - } - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - /// - /// See also the [`NaiveDate::ordinal`](./struct.NaiveDate.html#method.ordinal) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.ordinal(), 268); - /// ``` - #[inline] - fn ordinal(&self) -> u32 { - self.date.ordinal() - } - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - /// - /// See also the [`NaiveDate::ordinal0`](./struct.NaiveDate.html#method.ordinal0) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.ordinal0(), 267); - /// ``` - #[inline] - fn ordinal0(&self) -> u32 { - self.date.ordinal0() - } - - /// Returns the day of week. - /// - /// See also the [`NaiveDate::weekday`](./struct.NaiveDate.html#method.weekday) method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Weekday}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.weekday(), Weekday::Fri); - /// ``` - #[inline] - fn weekday(&self) -> Weekday { - self.date.weekday() - } - - #[inline] - fn iso_week(&self) -> IsoWeek { - self.date.iso_week() - } - - /// Makes a new `NaiveDateTime` with the year number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_year`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.with_year(2016), Some(NaiveDate::from_ymd(2016, 9, 25).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_year(-308), Some(NaiveDate::from_ymd(-308, 9, 25).and_hms(12, 34, 56))); - /// ``` - #[inline] - fn with_year(&self, year: i32) -> Option { - self.date.with_year(year).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_month`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month(10), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_month(13), None); // no month 13 - /// assert_eq!(dt.with_month(2), None); // no February 30 - /// ``` - #[inline] - fn with_month(&self, month: u32) -> Option { - self.date.with_month(month).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_month0`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month0(9), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_month0(12), None); // no month 13 - /// assert_eq!(dt.with_month0(1), None); // no February 30 - /// ``` - #[inline] - fn with_month0(&self, month0: u32) -> Option { - self.date.with_month0(month0).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_day`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day(30), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_day(31), None); // no September 31 - /// ``` - #[inline] - fn with_day(&self, day: u32) -> Option { - self.date.with_day(day).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_day0`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day0(29), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_day0(30), None); // no September 31 - /// ``` - #[inline] - fn with_day0(&self, day0: u32) -> Option { - self.date.with_day0(day0).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_ordinal`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); - /// ``` - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option { - self.date.with_ordinal(ordinal).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveDate::with_ordinal0`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); - /// ``` - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.date.with_ordinal0(ordinal0).map(|d| NaiveDateTime { date: d, ..*self }) - } -} - -impl Timelike for NaiveDateTime { - /// Returns the hour number from 0 to 23. - /// - /// See also the [`NaiveTime::hour`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.hour(), 12); - /// ``` - #[inline] - fn hour(&self) -> u32 { - self.time.hour() - } - - /// Returns the minute number from 0 to 59. - /// - /// See also the [`NaiveTime::minute`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.minute(), 34); - /// ``` - #[inline] - fn minute(&self) -> u32 { - self.time.minute() - } - - /// Returns the second number from 0 to 59. - /// - /// See also the [`NaiveTime::second`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.second(), 56); - /// ``` - #[inline] - fn second(&self) -> u32 { - self.time.second() - } - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// See also the [`NaiveTime::nanosecond`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.nanosecond(), 789_000_000); - /// ``` - #[inline] - fn nanosecond(&self) -> u32 { - self.time.nanosecond() - } - - /// Makes a new `NaiveDateTime` with the hour number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the [`NaiveTime::with_hour`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_hour(7), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(7, 34, 56, 789))); - /// assert_eq!(dt.with_hour(24), None); - /// ``` - #[inline] - fn with_hour(&self, hour: u32) -> Option { - self.time.with_hour(hour).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the minute number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveTime::with_minute`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_minute(45), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 45, 56, 789))); - /// assert_eq!(dt.with_minute(60), None); - /// ``` - #[inline] - fn with_minute(&self, min: u32) -> Option { - self.time.with_minute(min).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the second number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. As - /// with the [`NaiveDateTime::second`] method, the input range is - /// restricted to 0 through 59. - /// - /// See also the [`NaiveTime::with_second`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_second(17), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 17, 789))); - /// assert_eq!(dt.with_second(60), None); - /// ``` - #[inline] - fn with_second(&self, sec: u32) -> Option { - self.time.with_second(sec).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// As with the [`NaiveDateTime::nanosecond`] method, - /// the input range can exceed 1,000,000,000 for leap seconds. - /// - /// See also the [`NaiveTime::with_nanosecond`] method. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 333_333_333))); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 1_333_333_333))); - /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); - /// ``` - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option { - self.time.with_nanosecond(nano).map(|t| NaiveDateTime { time: t, ..*self }) - } -} - -/// `NaiveDateTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveDateTime { - fn hash(&self, state: &mut H) { - self.date.hash(state); - self.time.hash(state); - } -} - -/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_add_signed`] -/// to detect that. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); -/// assert_eq!(hms(3, 5, 7) + Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(-1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(3600 + 60), hms(4, 6, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(86_400), -/// from_ymd(2016, 7, 9).and_hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::days(365), -/// from_ymd(2017, 7, 8).and_hms(3, 5, 7)); -/// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); -/// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); -/// # } -/// ``` -/// -/// Leap seconds are handled, -/// but the addition assumes that it is the only leap second happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); -/// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), -/// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); -/// # } -/// ``` -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") - } -} - -impl AddAssign for NaiveDateTime { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. -/// It is the same as the addition with a negated `Duration`. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_sub_signed`] -/// to detect that. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); -/// assert_eq!(hms(3, 5, 7) - Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(-1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(3600 + 60), hms(2, 4, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(86_400), -/// from_ymd(2016, 7, 7).and_hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::days(365), -/// from_ymd(2015, 7, 9).and_hms(3, 5, 7)); -/// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); -/// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); -/// # } -/// ``` -/// -/// Leap seconds are handled, -/// but the subtraction assumes that it is the only leap second happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); -/// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), -/// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); -/// # } -/// ``` -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") - } -} - -impl SubAssign for NaiveDateTime { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveDateTime` from the current date and time. -/// This does not overflow or underflow at all. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the subtraction assumes that **there is no leap second ever**, -/// except when any of the `NaiveDateTime`s themselves represents a leap second -/// in which case the assumption becomes that -/// **there are exactly one (or two) leap second(s) ever**. -/// -/// The implementation is a wrapper around [`NaiveDateTime::signed_duration_since`]. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// assert_eq!(d.and_hms(3, 5, 7) - d.and_hms(2, 4, 6), Duration::seconds(3600 + 60 + 1)); -/// -/// // July 8 is 190th day in the year 2016 -/// let d0 = from_ymd(2016, 1, 1); -/// assert_eq!(d.and_hms_milli(0, 7, 6, 500) - d0.and_hms(0, 0, 0), -/// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); -/// # } -/// ``` -/// -/// Leap seconds are handled, but the subtraction assumes that no other leap -/// seconds happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms(23, 0, 0), -/// Duration::seconds(3600) + Duration::milliseconds(500)); -/// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0) - leap, -/// Duration::seconds(3600) - Duration::milliseconds(500)); -/// # } -/// ``` -impl Sub for NaiveDateTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveDateTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// The `Debug` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](::format::strftime). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveDate; -/// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); -/// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); -/// ``` -/// -/// Leap seconds may also be used. -/// -/// ``` -/// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); -/// ``` -impl fmt::Debug for NaiveDateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}T{:?}", self.date, self.time) - } -} - -/// The `Display` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](::format::strftime). -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveDate; -/// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); -/// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); -/// ``` -/// -/// Leap seconds may also be used. -/// -/// ``` -/// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); -/// ``` -impl fmt::Display for NaiveDateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.date, self.time) - } -} - -/// Parsing a `str` into a `NaiveDateTime` uses the same format, -/// [`%Y-%m-%dT%H:%M:%S%.f`](::format::strftime), as in `Debug`. -/// -/// # Example -/// -/// ``` -/// use chrono::{NaiveDateTime, NaiveDate}; -/// -/// let dt = NaiveDate::from_ymd(2015, 9, 18).and_hms(23, 56, 4); -/// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); -/// -/// let dt = NaiveDate::from_ymd(12345, 6, 7).and_hms_milli(7, 59, 59, 1_500); // leap second -/// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); -/// -/// assert!("foo".parse::().is_err()); -/// ``` -impl str::FromStr for NaiveDateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), - Item::Literal("T"), // XXX shouldn't this be case-insensitive? - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_datetime_with_offset(0) - } -} - -/// The default value for a NaiveDateTime is one with epoch 0 -/// that is, 1st of January 1970 at 00:00:00. -/// -/// # Example -/// -/// ```rust -/// use chrono::NaiveDateTime; -/// -/// let default_date = NaiveDateTime::default(); -/// assert_eq!(default_date, NaiveDateTime::from_timestamp(0, 0)); -/// ``` -impl Default for NaiveDateTime { - fn default() -> Self { - NaiveDateTime::from_timestamp(0, 0) - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDateTime) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - to_string(&NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)).ok(), - Some(r#""2016-07-08T09:10:48.090""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)).ok(), - Some(r#""0000-01-01T00:00:60""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)).ok(), - Some(r#""-0001-12-31T23:59:59.000000007""#.into()) - ); - assert_eq!( - to_string(&MIN_DATE.and_hms(0, 0, 0)).ok(), - Some(r#""-262144-01-01T00:00:00""#.into()) - ); - assert_eq!( - to_string(&MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(), - Some(r#""+262143-12-31T23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - from_str(r#""2016-07-08T09:10:48.090""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) - ); - assert_eq!( - from_str(r#""2016-7-8T9:10:48.09""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) - ); - assert_eq!( - from_str(r#""2014-07-24T12:34:06""#).ok(), - Some(NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)) - ); - assert_eq!( - from_str(r#""0000-01-01T00:00:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) - ); - assert_eq!( - from_str(r#""0-1-1T0:0:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) - ); - assert_eq!( - from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), - Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)) - ); - assert_eq!(from_str(r#""-262144-01-01T00:00:00""#).ok(), Some(MIN_DATE.and_hms(0, 0, 0))); - assert_eq!( - from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) - ); - assert_eq!( - from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""2016-07-08""#).is_err()); - assert!(from_str(r#""09:10:48.090""#).is_err()); - assert!(from_str(r#""20160708T091048.090""#).is_err()); - assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); - assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); - assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); - assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); - assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); - assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); - assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); - assert!(from_str(r#"20160708000000"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(all(test, feature = "rustc-serialize"))] -fn test_decodable_json_timestamp(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - *from_str("0").unwrap(), - NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), - "should parse integers as timestamps" - ); - assert_eq!( - *from_str("-1").unwrap(), - NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59), - "should parse integers as timestamps" - ); -} - -#[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { - use super::NaiveDateTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - use std::ops::Deref; - - impl Encodable for NaiveDateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDateTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) - } - } - - /// A `DateTime` that can be deserialized from a seconds-based timestamp - #[derive(Debug)] - #[deprecated( - since = "1.4.2", - note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" - )] - pub struct TsSeconds(NaiveDateTime); - - #[allow(deprecated)] - impl From for NaiveDateTime { - /// Pull the internal NaiveDateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> NaiveDateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = NaiveDateTime; - - #[allow(deprecated)] - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result { - Ok(TsSeconds( - NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) - .ok_or_else(|| d.error("invalid timestamp"))?, - )) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } - - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamp(json::decode); - } -} - -/// Tools to help serializing/deserializing `NaiveDateTime`s -#[cfg(feature = "serde")] -pub mod serde { - use super::NaiveDateTime; - use core::fmt; - use serdelib::{de, ser}; - - /// Serialize a `NaiveDateTime` as an RFC 3339 string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - impl ser::Serialize for NaiveDateTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct NaiveDateTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date and time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveDateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveDateTimeVisitor) - } - } - - /// Used to serialize/deserialize from nanosecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a `DateTime` from a nanoseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromNanoSecondsVisitor) - } - - struct NaiveDateTimeFromNanoSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromNanoSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value / 1_000_000_000, - (value % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value as i64 / 1_000_000_000, - (value as i64 % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from millisecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a milliseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromMilliSecondsVisitor) - } - - struct NaiveDateTimeFromMilliSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromMilliSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - (value / 1000) as i64, - ((value % 1000) * 1_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from second-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromSecondsVisitor) - } - - struct NaiveDateTimeFromSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value as i64, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_derive; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - #[test] - fn test_serde_bincode() { - use self::bincode::{deserialize, serialize, Infinite}; - use naive::NaiveDate; - - let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); - let encoded = serialize(&dt, Infinite).unwrap(); - let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - } - - #[test] - fn test_serde_bincode_optional() { - use self::bincode::{deserialize, serialize, Infinite}; - use self::serde_derive::{Deserialize, Serialize}; - use prelude::*; - use serde::ts_nanoseconds_option; - - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Test { - one: Option, - #[serde(with = "ts_nanoseconds_option")] - two: Option>, - } - - let expected = Test { one: Some(1), two: Some(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; - let bytes: Vec = serialize(&expected, Infinite).unwrap(); - let actual = deserialize::(&(bytes)).unwrap(); - - assert_eq!(expected, actual); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveDateTime; - use naive::{NaiveDate, MAX_DATE, MIN_DATE}; - use oldtime::Duration; - use std::i64; - use Datelike; - - #[test] - fn test_datetime_from_timestamp() { - let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); - assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); - assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); - assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); - assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); - assert_eq!(from_timestamp(i64::MIN), None); - assert_eq!(from_timestamp(i64::MAX), None); - } - - #[test] - fn test_datetime_add() { - fn check( - (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: Duration, - result: Option<(i32, u32, u32, u32, u32, u32)>, - ) { - let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let sum = - result.map(|(y, m, d, h, n, s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - } - - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(3600 + 60 + 1), - Some((2014, 5, 6, 8, 9, 10)), - ); - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(-(3600 + 60 + 1)), - Some((2014, 5, 6, 6, 7, 8)), - ); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - - // overflow check - // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. - // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = MAX_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((MAX_DATE.year(), 12, 31, 0, 0, 0))); - check( - (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + Duration::seconds(86399), - Some((MAX_DATE.year(), 12, 31, 23, 59, 59)), - ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); - - let min_days_from_year_0 = MIN_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((MIN_DATE.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); - } - - #[test] - fn test_datetime_sub() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let since = NaiveDateTime::signed_duration_since; - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::zero() - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::seconds(1) - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(-1) - ); - assert_eq!( - since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(86399) - ); - assert_eq!( - since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - Duration::seconds(999_999_999) - ); - } - - #[test] - fn test_datetime_addassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += Duration::days(10); - assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); - } - - #[test] - fn test_datetime_subassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= Duration::days(10); - assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); - } - - #[test] - fn test_datetime_timestamp() { - let to_timestamp = - |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s).timestamp(); - assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); - assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); - assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); - } - - #[test] - fn test_datetime_from_str() { - // valid cases - let valid = [ - "2015-2-18T23:16:9.15", - "-77-02-18T23:16:09", - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let ymdhmsn = - |y, m, d, h, n, s, nano| NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano); - assert_eq!( - NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56)) - ); // ignore offset - assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), - Ok(ymdhms(2015, 2, 2, 0, 0, 0)) - ); - assert_eq!( - NaiveDateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ), - Ok(ymdhms(2013, 8, 9, 23, 54, 35)) - ); - assert!(NaiveDateTime::parse_from_str( - "Sat, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); - assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient - assert_eq!( - NaiveDateTime::parse_from_str("1441497364", "%s"), - Ok(ymdhms(2015, 9, 5, 23, 56, 4)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), - Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), - Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), - Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), - Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) - ); - } - - #[test] - fn test_datetime_format() { - let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321); - assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); - assert_eq!(dt.format("%s").to_string(), "1283929614"); - assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // a horror of leap second: coming near to you. - let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000); - assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); - assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. - } - - #[test] - fn test_datetime_add_sub_invariant() { - // issue #37 - let base = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); - let t = -946684799990000; - let time = base + Duration::microseconds(t); - assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); - } - - #[test] - fn test_nanosecond_range() { - const A_BILLION: i64 = 1_000_000_000; - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - - let minimum = "1677-09-21T00:12:44.000000000"; - let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - } -} diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs new file mode 100644 index 0000000000..0723c4866f --- /dev/null +++ b/src/naive/datetime/mod.rs @@ -0,0 +1,2171 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 date and time without timezone. + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::fmt::Write; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::format::{ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::Utc; +use crate::time_delta::NANOS_PER_SEC; +use crate::{ + DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, + Weekday, expect, try_opt, +}; + +/// Tools to help serializing/deserializing `NaiveDateTime`s +#[cfg(feature = "serde")] +pub(crate) mod serde; + +#[cfg(test)] +mod tests; + +/// The minimum possible `NaiveDateTime`. +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] +pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; +/// The maximum possible `NaiveDateTime`. +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MAX instead")] +pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; + +/// ISO 8601 combined date and time without timezone. +/// +/// # Example +/// +/// `NaiveDateTime` is commonly created from [`NaiveDate`]. +/// +/// ``` +/// use chrono::{NaiveDate, NaiveDateTime}; +/// +/// let dt: NaiveDateTime = +/// NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// # let _ = dt; +/// ``` +/// +/// You can use typical [date-like](Datelike) and [time-like](Timelike) methods, +/// provided that relevant traits are in the scope. +/// +/// ``` +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// use chrono::{Datelike, Timelike, Weekday}; +/// +/// assert_eq!(dt.weekday(), Weekday::Fri); +/// assert_eq!(dt.num_seconds_from_midnight(), 33011); +/// ``` +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +pub struct NaiveDateTime { + date: NaiveDate, + time: NaiveTime, +} + +impl NaiveDateTime { + /// Makes a new `NaiveDateTime` from date and time components. + /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) + /// and many other helper constructors on `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// + /// let dt = NaiveDateTime::new(d, t); + /// assert_eq!(dt.date(), d); + /// assert_eq!(dt.time(), t); + /// ``` + #[inline] + pub const fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { + NaiveDateTime { date, time } + } + + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, + /// from the number of non-leap seconds + /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// For a non-naive version of this function see [`TimeZone::timestamp`]. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Panics + /// + /// Panics if the number of seconds would be out of range for a `NaiveDateTime` (more than + /// ca. 262,000 years away from common era), and panics on an invalid nanosecond (2 seconds or + /// more). + #[deprecated(since = "0.4.23", note = "use `DateTime::from_timestamp` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { + let datetime = + expect(DateTime::from_timestamp(secs, nsecs), "invalid or out-of-range datetime"); + datetime.naive_utc() + } + + /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of milliseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_millis` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_millis(millis: i64) -> Option { + Some(try_opt!(DateTime::from_timestamp_millis(millis)).naive_utc()) + } + + /// Creates a new [NaiveDateTime] from microseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_micros` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_micros(micros: i64) -> Option { + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + Some(try_opt!(DateTime::::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_nanos` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Option { + let secs = nanos.div_euclid(NANOS_PER_SEC as i64); + let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, + /// from the number of non-leap seconds + /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` if the number of seconds would be out of range for a `NaiveDateTime` (more + /// than ca. 262,000 years away from common era), and panics on an invalid nanosecond + /// (2 seconds or more). + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Parses a string with the specified format string and returns a new `NaiveDateTime`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let parse_from_str = NaiveDateTime::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_micro_opt(13, 23, 45, 678_900) + /// .unwrap()) + /// ); + /// ``` + /// + /// Offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + /// + /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by + /// treating any time of the form `hh:mm:60` as a leap second. + /// (This equally applies to the formatting, so the round trip is possible.) + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_milli_opt(8, 59, 59, 1_123) + /// .unwrap()) + /// ); + /// ``` + /// + /// Missing seconds are assumed to be zero, + /// but out-of-bound times or insufficient fields are errors otherwise. + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), + /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap()) + /// ); + /// + /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); + /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); + /// assert!(parse_from_str("94/9/4 17:60", "%y/%m/%d %H:%M").is_err()); + /// assert!(parse_from_str("94/9/4 24:00:00", "%y/%m/%d %H:%M:%S").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// + /// ``` + /// # use chrono::NaiveDateTime; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S = UNIX timestamp %s"; + /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); + /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); + /// ``` + /// + /// Years before 1 BCE or after 9999 CE, require an initial sign + /// + ///``` + /// # use chrono::NaiveDateTime; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S"; + /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); + /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0) // no offset adjustment + } + + /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a + /// slice with the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// let (datetime, remainder) = NaiveDateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 trailing text", + /// "%Y-%m-%d %H:%M:%S", + /// ) + /// .unwrap(); + /// assert_eq!( + /// datetime, + /// NaiveDate::from_ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0).map(|d| (d, remainder)) // no offset adjustment + } + + /// Retrieves a date component. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); + /// ``` + #[inline] + pub const fn date(&self) -> NaiveDate { + self.date + } + + /// Retrieves a time component. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveTime}; + /// + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); + /// ``` + #[inline] + pub const fn time(&self) -> NaiveTime { + self.time + } + + /// Returns the number of non-leap seconds since the midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp()` instead")] + #[inline] + #[must_use] + pub const fn timestamp(&self) -> i64 { + self.and_utc().timestamp() + } + + /// Returns the number of non-leap *milliseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_millis()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_millis(&self) -> i64 { + self.and_utc().timestamp_millis() + } + + /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_micros()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_micros(&self) -> i64 { + self.and_utc().timestamp_micros() + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.31", note = "use `.and_utc().timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + #[allow(deprecated)] + pub const fn timestamp_nanos(&self) -> i64 { + self.and_utc().timestamp_nanos() + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_nanos_opt(&self) -> Option { + self.and_utc().timestamp_nanos_opt() + } + + /// Returns the number of milliseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_millis()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_subsec_millis(&self) -> u32 { + self.and_utc().timestamp_subsec_millis() + } + + /// Returns the number of microseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999,999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_micros()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_subsec_micros(&self) -> u32 { + self.and_utc().timestamp_subsec_micros() + } + + /// Returns the number of nanoseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999,999,999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. + #[deprecated(since = "0.4.36", note = "use `.and_utc().timestamp_subsec_nanos()` instead")] + pub const fn timestamp_subsec_nanos(&self) -> u32 { + self.and_utc().timestamp_subsec_nanos() + } + + /// Adds given `TimeDelta` to the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the addition assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(4, 6, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(86_400).unwrap()), + /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()) + /// ); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); + /// assert_eq!( + /// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::try_milliseconds(450).unwrap()), + /// Some(hmsm(3, 5, 8, 430)) + /// ); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the addition assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_add_signed(TimeDelta::zero()), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(-500).unwrap()), + /// Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(500).unwrap()), + /// Some(hmsm(3, 5, 59, 1_800))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(800).unwrap()), + /// Some(hmsm(3, 6, 0, 100))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(10).unwrap()), + /// Some(hmsm(3, 6, 9, 300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(-10).unwrap()), + /// Some(hmsm(3, 5, 50, 300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_days(1).unwrap()), + /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); + /// ``` + #[must_use] + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { + let (time, remainder) = self.time.overflowing_add_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_add_signed(remainder)); + Some(NaiveDateTime { date, time }) + } + + /// Adds given `Months` to the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_add_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_months(rhs)), time: self.time }) + } + + /// Adds given `FixedOffset` to the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_add_signed`](#method.checked_add_offset), but preserves + /// leap seconds. + #[must_use] + pub const fn checked_add_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_sub_signed`](#method.checked_sub_signed), but preserves + /// leap seconds. + pub const fn checked_sub_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Adds given `FixedOffset` to the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + pub(crate) fn overflowing_add_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + #[allow(unused)] // currently only used in `Local` but not on all platforms + pub(crate) fn overflowing_sub_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + + /// Subtracts given `TimeDelta` from the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(2, 4, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(86_400).unwrap()), + /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()) + /// ); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); + /// assert_eq!( + /// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::try_milliseconds(670).unwrap()), + /// Some(hmsm(3, 5, 6, 780)) + /// ); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the subtraction assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(200).unwrap()), + /// Some(hmsm(3, 5, 59, 1_100))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(500).unwrap()), + /// Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_seconds(60).unwrap()), + /// Some(hmsm(3, 5, 0, 300))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_days(1).unwrap()), + /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); + /// ``` + #[must_use] + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { + let (time, remainder) = self.time.overflowing_sub_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_sub_signed(remainder)); + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `Months` from the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2013, 12, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_months(rhs)), time: self.time }) + } + + /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + #[must_use] + pub const fn checked_add_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_days(days)), ..self }) + } + + /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + #[must_use] + pub const fn checked_sub_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_days(days)), ..self }) + } + + /// Subtracts another `NaiveDateTime` from the current date and time. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveDateTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// assert_eq!( + /// d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + /// ); + /// + /// // July 8 is 190th day in the year 2016 + /// let d0 = from_ymd(2016, 1, 1); + /// assert_eq!( + /// d.and_hms_milli_opt(0, 7, 6, 500) + /// .unwrap() + /// .signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), + /// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + /// + TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); + /// assert_eq!( + /// leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), + /// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// assert_eq!( + /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), + /// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { + expect( + self.date + .signed_duration_since(rhs.date) + .checked_add(&self.time.signed_duration_since(rhs.time)), + "always in range", + ) + } + + /// Formats the combined date and time with the specified formatting items. + /// Otherwise it is the same as the ordinary [`format`](#method.format) method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; + /// + /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(Some(self.date), Some(self.time), items) + } + + /// Formats the combined date and time with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// A wrong format string does *not* issue an error immediately. + /// Rather, converting or formatting the `DelayedFormat` fails. + /// You are recommended to immediately use `DelayedFormat` for this reason. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); + /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Converts the `NaiveDateTime` into a timezone-aware `DateTime` with the provided + /// time zone. + /// + /// # Example + /// + /// ``` + /// use chrono::{FixedOffset, NaiveDate}; + /// let hour = 3600; + /// let tz = FixedOffset::east_opt(5 * hour).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_opt(23, 56, 4) + /// .unwrap() + /// .and_local_timezone(tz) + /// .unwrap(); + /// assert_eq!(dt.timezone(), tz); + /// ``` + #[must_use] + pub fn and_local_timezone(&self, tz: Tz) -> MappedLocalTime> { + tz.from_local_datetime(self) + } + + /// Converts the `NaiveDateTime` into the timezone-aware `DateTime`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// let dt = + /// NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); + /// assert_eq!(dt.timezone(), Utc); + /// ``` + #[must_use] + pub const fn and_utc(&self) -> DateTime { + DateTime::from_naive_utc_and_offset(*self, Utc) + } + + /// The minimum possible `NaiveDateTime`. + pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; + + /// The maximum possible `NaiveDateTime`. + pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; + + /// The datetime of the Unix Epoch, 1970-01-01 00:00:00. + /// + /// Note that while this may look like the UNIX epoch, it is missing the + /// time zone. The actual UNIX epoch cannot be expressed by this type, + /// however it is available as [`DateTime::UNIX_EPOCH`]. + #[deprecated(since = "0.4.41", note = "use `DateTime::UNIX_EPOCH` instead")] + pub const UNIX_EPOCH: Self = DateTime::UNIX_EPOCH.naive_utc(); +} + +impl From for NaiveDateTime { + /// Converts a `NaiveDate` to a `NaiveDateTime` of the same date but at midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let nd = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap(); + /// let ndt = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// assert_eq!(ndt, NaiveDateTime::from(nd)); + fn from(date: NaiveDate) -> Self { + date.and_hms_opt(0, 0, 0).unwrap() + } +} + +impl Datelike for NaiveDateTime { + /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). + /// + /// See also the [`NaiveDate::year`](./struct.NaiveDate.html#method.year) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.year(), 2015); + /// ``` + #[inline] + fn year(&self) -> i32 { + self.date.year() + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + /// + /// See also the [`NaiveDate::month`](./struct.NaiveDate.html#method.month) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.month(), 9); + /// ``` + #[inline] + fn month(&self) -> u32 { + self.date.month() + } + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// See also the [`NaiveDate::month0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.month0(), 8); + /// ``` + #[inline] + fn month0(&self) -> u32 { + self.date.month0() + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + /// + /// See also the [`NaiveDate::day`](./struct.NaiveDate.html#method.day) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.day(), 25); + /// ``` + #[inline] + fn day(&self) -> u32 { + self.date.day() + } + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// See also the [`NaiveDate::day0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.day0(), 24); + /// ``` + #[inline] + fn day0(&self) -> u32 { + self.date.day0() + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + /// + /// See also the [`NaiveDate::ordinal`](./struct.NaiveDate.html#method.ordinal) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.ordinal(), 268); + /// ``` + #[inline] + fn ordinal(&self) -> u32 { + self.date.ordinal() + } + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// See also the [`NaiveDate::ordinal0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.ordinal0(), 267); + /// ``` + #[inline] + fn ordinal0(&self) -> u32 { + self.date.ordinal0() + } + + /// Returns the day of week. + /// + /// See also the [`NaiveDate::weekday`](./struct.NaiveDate.html#method.weekday) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Weekday}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.weekday(), Weekday::Fri); + /// ``` + #[inline] + fn weekday(&self) -> Weekday { + self.date.weekday() + } + + #[inline] + fn iso_week(&self) -> IsoWeek { + self.date.iso_week() + } + + /// Makes a new `NaiveDateTime` with the year number changed, while keeping the same month and + /// day. + /// + /// See also the [`NaiveDate::with_year`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_year(&self, year: i32) -> Option { + self.date.with_year(year).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// See also the [`NaiveDate::with_month`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_month(13), None); // No month 13 + /// assert_eq!(dt.with_month(2), None); // No February 30 + /// ``` + #[inline] + fn with_month(&self, month: u32) -> Option { + self.date.with_month(month).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_month0(12), None); // No month 13 + /// assert_eq!(dt.with_month0(1), None); // No February 30 + /// ``` + #[inline] + fn with_month0(&self, month0: u32) -> Option { + self.date.with_month0(month0).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_day`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_day(31), None); // no September 31 + /// ``` + #[inline] + fn with_day(&self, day: u32) -> Option { + self.date.with_day(day).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_day0(30), None); // no September 31 + /// ``` + #[inline] + fn with_day0(&self, day0: u32) -> Option { + self.date.with_day0(day0).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_ordinal`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option { + self.date.with_ordinal(ordinal).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option { + self.date.with_ordinal0(ordinal0).map(|d| NaiveDateTime { date: d, ..*self }) + } +} + +impl Timelike for NaiveDateTime { + /// Returns the hour number from 0 to 23. + /// + /// See also the [`NaiveTime::hour`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.hour(), 12); + /// ``` + #[inline] + fn hour(&self) -> u32 { + self.time.hour() + } + + /// Returns the minute number from 0 to 59. + /// + /// See also the [`NaiveTime::minute`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.minute(), 34); + /// ``` + #[inline] + fn minute(&self) -> u32 { + self.time.minute() + } + + /// Returns the second number from 0 to 59. + /// + /// See also the [`NaiveTime::second`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.second(), 56); + /// ``` + #[inline] + fn second(&self) -> u32 { + self.time.second() + } + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// + /// See also the [`NaiveTime#method.nanosecond`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.nanosecond(), 789_000_000); + /// ``` + #[inline] + fn nanosecond(&self) -> u32 { + self.time.nanosecond() + } + + /// Makes a new `NaiveDateTime` with the hour number changed. + /// + /// See also the [`NaiveTime::with_hour`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_hour(7), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_hour(24), None); + /// ``` + #[inline] + fn with_hour(&self, hour: u32) -> Option { + self.time.with_hour(hour).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the minute number changed. + /// + /// See also the [`NaiveTime::with_minute`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_minute(45), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 45, 56, 789) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_minute(60), None); + /// ``` + #[inline] + fn with_minute(&self, min: u32) -> Option { + self.time.with_minute(min).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// See also the [`NaiveTime::with_second`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_second(17), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 34, 17, 789) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_second(60), None); + /// ``` + #[inline] + fn with_second(&self, sec: u32) -> Option { + self.time.with_second(sec).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// As with the [`NaiveDateTime::nanosecond`] method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// See also the [`NaiveTime::with_nanosecond`] method. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 333_333_333) + /// .unwrap() + /// ) + /// ); + /// assert_eq!( + /// dt.with_nanosecond(1_333_333_333), // leap second + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 1_333_333_333) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); + /// ``` + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option { + self.time.with_nanosecond(nano).map(|t| NaiveDateTime { time: t, ..*self }) + } +} + +/// Add `TimeDelta` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(3600 + 60).unwrap(), hms(4, 6, 7)); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::try_seconds(86_400).unwrap(), +/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::try_days(365).unwrap(), +/// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); +/// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::try_milliseconds(450).unwrap(), hmsm(3, 5, 8, 430)); +/// ``` +/// +/// Leap seconds are handled, +/// but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), +/// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn add(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") + } +} + +/// Add `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn add(self, rhs: Duration) -> NaiveDateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") + } +} + +/// Add-assign `TimeDelta` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDateTime { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add-assign `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDateTime { + #[inline] + #[track_caller] + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + +/// Add `FixedOffset` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_offset` to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn add(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_add_offset(rhs).expect("`NaiveDateTime + FixedOffset` out of range") + } +} + +/// Add `Months` to `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// + Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// + Months::new(12), +/// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// + Months::new(13), +/// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// + Months::new(1), +/// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// ); +/// ``` +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[track_caller] + fn add(self, rhs: Months) -> Self::Output { + self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") + } +} + +/// Subtract `TimeDelta` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(3600 + 60).unwrap(), hms(2, 4, 7)); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::try_seconds(86_400).unwrap(), +/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::try_days(365).unwrap(), +/// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); +/// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::try_milliseconds(670).unwrap(), hmsm(3, 5, 6, 780)); +/// ``` +/// +/// Leap seconds are handled, +/// but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), +/// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn sub(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") + } +} + +/// Subtract `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn sub(self, rhs: Duration) -> NaiveDateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDateTime { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtract-assign `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDateTime { + #[inline] + #[track_caller] + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs); + } +} + +/// Subtract `FixedOffset` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_offset` to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + #[track_caller] + fn sub(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_sub_offset(rhs).expect("`NaiveDateTime - FixedOffset` out of range") + } +} + +/// Subtract `Months` from `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`NaiveDateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_months`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() +/// - Months::new(11), +/// NaiveDate::from_ymd_opt(2013, 02, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() +/// - Months::new(12), +/// NaiveDate::from_ymd_opt(2013, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() +/// - Months::new(13), +/// NaiveDate::from_ymd_opt(2012, 12, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() +/// ); +/// ``` +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[track_caller] + fn sub(self, rhs: Months) -> Self::Output { + self.checked_sub_months(rhs).expect("`NaiveDateTime - Months` out of range") + } +} + +/// Subtracts another `NaiveDateTime` from the current date and time. +/// This does not overflow or underflow at all. +/// +/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), +/// the subtraction assumes that **there is no leap second ever**, +/// except when any of the `NaiveDateTime`s themselves represents a leap second +/// in which case the assumption becomes that +/// **there are exactly one (or two) leap second(s) ever**. +/// +/// The implementation is a wrapper around [`NaiveDateTime::signed_duration_since`]. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// assert_eq!( +/// d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() +/// ); +/// +/// // July 8 is 190th day in the year 2016 +/// let d0 = from_ymd(2016, 1, 1); +/// assert_eq!( +/// d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), +/// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() +/// + TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that no other leap +/// seconds happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!( +/// leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), +/// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// assert_eq!( +/// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, +/// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// ``` +impl Sub for NaiveDateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveDateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_days` to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[track_caller] + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`NaiveDateTime + Days` out of range") + } +} + +/// Subtract `Days` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_days` to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[track_caller] + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`NaiveDateTime - Days` out of range") + } +} + +/// The `Debug` output of the naive date and time `dt` is the same as +/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); +/// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveDate; +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); +/// ``` +impl fmt::Debug for NaiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.date.fmt(f)?; + f.write_char('T')?; + self.time.fmt(f) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveDateTime { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}T{}", self.date, self.time); + } +} + +/// The `Display` output of the naive date and time `dt` is the same as +/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](crate::format::strftime). +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); +/// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveDate; +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); +/// ``` +impl fmt::Display for NaiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.date.fmt(f)?; + f.write_char(' ')?; + self.time.fmt(f) + } +} + +/// Parsing a `str` into a `NaiveDateTime` uses the same format, +/// [`%Y-%m-%dT%H:%M:%S%.f`](crate::format::strftime), as in `Debug`. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDateTime, NaiveDate}; +/// +/// let dt = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap().and_hms_opt(23, 56, 4).unwrap(); +/// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); +/// +/// let dt = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap().and_hms_milli_opt(7, 59, 59, 1_500).unwrap(); // leap second +/// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveDateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), + Item::Literal("T"), // XXX shouldn't this be case-insensitive? + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; + + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_naive_datetime_with_offset(0) + } +} + +/// The default value for a NaiveDateTime is 1st of January 1970 at 00:00:00. +/// +/// Note that while this may look like the UNIX epoch, it is missing the +/// time zone. The actual UNIX epoch cannot be expressed by this type, +/// however it is available as [`DateTime::UNIX_EPOCH`]. +impl Default for NaiveDateTime { + fn default() -> Self { + DateTime::UNIX_EPOCH.naive_local() + } +} diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs new file mode 100644 index 0000000000..6ebdefaee5 --- /dev/null +++ b/src/naive/datetime/serde.rs @@ -0,0 +1,1304 @@ +use core::fmt; +use serde::{de, ser}; + +use super::NaiveDateTime; + +/// Serialize a `NaiveDateTime` as an ISO 8601 string +/// +/// See [the `naive::serde` module](crate::naive::serde) for alternate serialization formats. +impl ser::Serialize for NaiveDateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl fmt::Display for FormatWrapped<'_, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + serializer.collect_str(&FormatWrapped { inner: &self }) + } +} + +struct NaiveDateTimeVisitor; + +impl de::Visitor<'_> for NaiveDateTimeVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date and time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveDateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveDateTimeVisitor) + } +} + +/// Used to serialize/deserialize from nanosecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) + } + + /// Deserialize a `NaiveDateTime` from a nanoseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor) + } + + pub(super) struct NanoSecondsTimestampVisitor; + + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, + ) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_nanoseconds::NanoSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from microsecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_micros()) + } + + /// Deserialize a `NaiveDateTime` from a microseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor) + } + + pub(super) struct MicroSecondsTimestampVisitor; + + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_micros(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, + ) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_microseconds::MicroSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from millisecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_millis()) + } + + /// Deserialize a `NaiveDateTime` from a milliseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor) + } + + pub(super) struct MilliSecondsTimestampVisitor; + + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_millis(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_milliseconds::MilliSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from second-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = + /// S { time: NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap() }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp()) + } + + /// Deserialize a `NaiveDateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor) + } + + pub(super) struct SecondsTimestampVisitor; + + impl de::Visitor<'_> for SecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_secs(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + DateTime::from_timestamp_secs(value as i64) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option, +/// } +/// +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_seconds::SecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option, + /// } + /// + /// let expected = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap(); + /// let my_s = S { time: Some(expected) }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a second timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use crate::serde::ts_nanoseconds_option; + use crate::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc}; + + use bincode::{deserialize, serialize}; + use serde_derive::{Deserialize, Serialize}; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + .ok(), + Some(r#""2016-07-08T09:10:48.090""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + .ok(), + Some(r#""0000-01-01T00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + .ok(), + Some(r#""-0001-12-31T23:59:59.000000007""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""-262143-01-01T00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap() + ) + .ok(), + Some(r#""+262142-12-31T23:59:60.999999999""#.into()) + ); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08T09:10:48.090""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2016-7-8T9:10:48.09""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2014-07-24T12:34:06""#).ok(), + Some(NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) + ); + assert_eq!( + from_str(r#""0000-01-01T00:00:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""0-1-1T0:0:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), + Some( + NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-262143-01-01T00:00:00""#).ok(), + Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""2016-07-08""#).is_err()); + assert!(from_str(r#""09:10:48.090""#).is_err()); + assert!(from_str(r#""20160708T091048.090""#).is_err()); + assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); + assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); + assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); + assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); + assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); + assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); + assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); + assert!(from_str(r#"20160708000000"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); + // pre-0.3.0 rustc-serialize format is now invalid + assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); + assert!(from_str(r#"null"#).is_err()); + } + + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + #[test] + fn test_serde_bincode() { + let dt = + NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + } + + #[test] + fn test_serde_bincode_optional() { + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test { + one: Option, + #[serde(with = "ts_nanoseconds_option")] + two: Option>, + } + + let expected = + Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) }; + let bytes: Vec = serialize(&expected).unwrap(); + let actual = deserialize::(&(bytes)).unwrap(); + + assert_eq!(expected, actual); + } +} diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs new file mode 100644 index 0000000000..d6148d6799 --- /dev/null +++ b/src/naive/datetime/tests.rs @@ -0,0 +1,405 @@ +use super::NaiveDateTime; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; + +#[test] +fn test_datetime_add() { + fn check( + (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), + rhs: TimeDelta, + result: Option<(i32, u32, u32, u32, u32, u32)>, + ) { + let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let sum = result.map(|(y, m, d, h, n, s)| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }); + assert_eq!(lhs.checked_add_signed(rhs), sum); + assert_eq!(lhs.checked_sub_signed(-rhs), sum); + } + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + + // overflow check + // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. + // (they are private constants, but the equivalence is tested in that module.) + let max_days_from_year_0 = + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); + check( + (0, 1, 1, 0, 0, 0), + max_days_from_year_0 + seconds(86399), + Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), + ); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MAX, None); + + let min_days_from_year_0 = + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MIN, None); +} + +#[test] +fn test_datetime_sub() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let since = NaiveDateTime::signed_duration_since; + assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero()); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), + TimeDelta::try_seconds(1).unwrap() + ); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + TimeDelta::try_seconds(-1).unwrap() + ); + assert_eq!( + since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + TimeDelta::try_seconds(86399).unwrap() + ); + assert_eq!( + since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), + TimeDelta::try_seconds(999_999_999).unwrap() + ); +} + +#[test] +fn test_datetime_addassignment() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date += TimeDelta::try_minutes(10_000_000).unwrap(); + assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); + date += TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); +} + +#[test] +fn test_datetime_subassignment() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date -= TimeDelta::try_minutes(10_000_000).unwrap(); + assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); + date -= TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + let same = dt + Duration::ZERO; + assert_eq!(dt, same); + + dt += Duration::new(3600, 0); + assert_eq!(dt, NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + +#[test] +fn test_datetime_from_str() { + // valid cases + let valid = [ + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", + "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", + ]; + for &s in &valid { + eprintln!("test_parse_naivedatetime valid {s:?}"); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{s}` has failed: {e}"), + }; + let s_ = format!("{d:?}"); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") + } + }; + assert!( + d == d_, + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {s:?}"); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_datetime_parse_from_str() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let ymdhmsn = |y, m, d, h, n, s, nano| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap() + }; + assert_eq!( + NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(2014, 5, 7, 12, 34, 56)) + ); // ignore offset + assert_eq!( + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), + Ok(ymdhms(2015, 2, 2, 0, 0, 0)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), + Ok(ymdhms(2013, 8, 9, 23, 54, 35)) + ); + assert!( + NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); + assert!(NaiveDateTime::parse_from_str("2014-5-7 Q2 12:3456", "%Y-%m-%d Q%q %H:%M:%S").is_err()); + assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient + assert_eq!( + NaiveDateTime::parse_from_str("1441497364", "%s"), + Ok(ymdhms(2015, 9, 5, 23, 56, 4)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), + Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), + Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), + Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), + Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) + ); +} + +#[test] +fn test_datetime_parse_from_str_with_spaces() { + let parse_from_str = NaiveDateTime::parse_from_str; + let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + // with varying spaces - should fail + // leading space in data + assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_add_sub_invariant() { + // issue #37 + let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let t = -946684799990000; + let time = base + TimeDelta::microseconds(t); + assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); +} + +#[test] +fn test_and_local_timezone() { + let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); + let dt_utc = ndt.and_utc(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); + + let offset_tz = FixedOffset::west_opt(4 * 3600).unwrap(); + let dt_offset = ndt.and_local_timezone(offset_tz).unwrap(); + assert_eq!(dt_offset.naive_local(), ndt); + assert_eq!(dt_offset.timezone(), offset_tz); +} + +#[test] +fn test_and_utc() { + let ndt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap(); + let dt_utc = ndt.and_utc(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); +} + +#[test] +fn test_checked_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none()); +} + +#[test] +fn test_checked_sub_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none()); + + assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); + assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); +} + +#[test] +fn test_overflowing_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() + }; + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN); +} + +#[test] +fn test_and_timezone_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = NaiveDateTime::MAX.and_local_timezone(offset); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, MappedLocalTime::None); + } + let local_min = NaiveDateTime::MIN.and_local_timezone(offset); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, MappedLocalTime::None); + } + } +} + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let dt_min = NaiveDateTime::MIN; + let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_min); + + let dt_max = NaiveDateTime::MAX; + let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_max); +} diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 4d7c1b3bf1..547dc46ad5 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -1,59 +1,52 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! The internal implementation of the calendar and ordinal date. -//! -//! The current implementation is optimized for determining year, month, day and day of week. -//! 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar, -//! which are included in every packed `NaiveDate` instance. -//! The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is -//! based on the moderately-sized lookup table (~1.5KB) -//! and the packed representation is chosen for the efficient lookup. -//! Every internal data structure does not validate its input, -//! but the conversion keeps the valid value valid and the invalid value invalid -//! so that the user-facing `NaiveDate` can validate the input as late as possible. - -#![allow(dead_code)] // some internal methods have been left for consistency -#![cfg_attr(feature = "__internal_bench", allow(missing_docs))] - -use core::{fmt, i32}; -use div::{div_rem, mod_floor}; -use num_traits::FromPrimitive; -use Weekday; +//! Internal helper types for working with dates. -/// The internal date representation. This also includes the packed `Mdf` value. -pub type DateImpl = i32; +#![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -pub const MAX_YEAR: DateImpl = i32::MAX >> 13; -pub const MIN_YEAR: DateImpl = i32::MIN >> 13; +use core::fmt; -/// The year flags (aka the dominical letter). +/// Year flags (aka the dominical letter). +/// +/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`. /// /// There are 14 possible classes of year in the Gregorian calendar: /// common and leap years starting with Monday through Sunday. -/// The `YearFlags` stores this information into 4 bits `abbb`, -/// where `a` is `1` for the common year (simplifies the `Of` validation) -/// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year -/// (simplifies the day of week calculation from the 1-based ordinal). -#[derive(PartialEq, Eq, Copy, Clone)] -pub struct YearFlags(pub u8); - -pub const A: YearFlags = YearFlags(0o15); -pub const AG: YearFlags = YearFlags(0o05); -pub const B: YearFlags = YearFlags(0o14); -pub const BA: YearFlags = YearFlags(0o04); -pub const C: YearFlags = YearFlags(0o13); -pub const CB: YearFlags = YearFlags(0o03); -pub const D: YearFlags = YearFlags(0o12); -pub const DC: YearFlags = YearFlags(0o02); -pub const E: YearFlags = YearFlags(0o11); -pub const ED: YearFlags = YearFlags(0o01); -pub const F: YearFlags = YearFlags(0o17); -pub const FE: YearFlags = YearFlags(0o07); -pub const G: YearFlags = YearFlags(0o16); -pub const GF: YearFlags = YearFlags(0o06); - -static YEAR_TO_FLAGS: [YearFlags; 400] = [ +/// +/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1` +/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero +/// `Weekday` of the last day in the preceding year. +#[allow(unreachable_pub)] // public as an alias for benchmarks only +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +pub struct YearFlags(pub(super) u8); + +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`. +const YEAR_STARTS_AFTER_THUESDAY: u8 = 1; +const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2; +const YEAR_STARTS_AFTER_THURSDAY: u8 = 3; +const YEAR_STARTS_AFTER_FRIDAY: u8 = 4; +const YEAR_STARTS_AFTER_SATURDAY: u8 = 5; +const YEAR_STARTS_AFTER_SUNDAY: u8 = 6; + +const COMMON_YEAR: u8 = 1 << 3; +const LEAP_YEAR: u8 = 0 << 3; + +pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY); +pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY); + +const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, @@ -72,66 +65,31 @@ static YEAR_TO_FLAGS: [YearFlags; 400] = [ D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 ]; -static YEAR_DELTAS: [u8; 401] = [ - 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, - 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, - 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, - 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 - 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, - 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, - 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, - 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, - 48, 49, 49, 49, // 200 - 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, - 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, - 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, - 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, - 72, 73, 73, 73, // 300 - 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, - 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, - 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, - 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, - 96, 97, 97, 97, 97, // 400+1 -]; - -pub fn cycle_to_yo(cycle: u32) -> (u32, u32) { - let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365); - let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]); - if ordinal0 < delta { - year_mod_400 -= 1; - ordinal0 += 365 - u32::from(YEAR_DELTAS[year_mod_400 as usize]); - } else { - ordinal0 -= delta; - } - (year_mod_400, ordinal0 + 1) -} - -pub fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { - year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1 -} - impl YearFlags { + #[allow(unreachable_pub)] // public as an alias for benchmarks only + #[doc(hidden)] // for benchmarks only #[inline] - pub fn from_year(year: i32) -> YearFlags { - let year = mod_floor(year, 400); + #[must_use] + pub const fn from_year(year: i32) -> YearFlags { + let year = year.rem_euclid(400); YearFlags::from_year_mod_400(year) } #[inline] - pub fn from_year_mod_400(year: i32) -> YearFlags { + pub(super) const fn from_year_mod_400(year: i32) -> YearFlags { YEAR_TO_FLAGS[year as usize] } #[inline] - pub fn ndays(&self) -> u32 { + pub(super) const fn ndays(&self) -> u32 { let YearFlags(flags) = *self; - 366 - u32::from(flags >> 3) + 366 - (flags >> 3) as u32 } #[inline] - pub fn isoweek_delta(&self) -> u32 { + pub(super) const fn isoweek_delta(&self) -> u32 { let YearFlags(flags) = *self; - let mut delta = u32::from(flags) & 0b0111; + let mut delta = (flags & 0b0111) as u32; if delta < 3 { delta += 7; } @@ -139,7 +97,7 @@ impl YearFlags { } #[inline] - pub fn nisoweeks(&self) -> u32 { + pub(super) const fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } @@ -165,18 +123,20 @@ impl fmt::Debug for YearFlags { 0o07 => "FE".fmt(f), 0o16 => "G".fmt(f), 0o06 => "GF".fmt(f), - _ => write!(f, "YearFlags({})", flags), + _ => write!(f, "YearFlags({flags})"), } } } -pub const MIN_OL: u32 = 1 << 1; -pub const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` -pub const MIN_MDL: u32 = (1 << 6) | (1 << 1); -pub const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; +// OL: (ordinal << 1) | leap year flag +const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year +const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; -const XX: i8 = -128; -static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ +// The next table are adjustment values to convert a date encoded as month-day-leapyear to +// ordinal-leapyear. OL = MDL - adjustment. +// Dates that do not exist are encoded as `XX`. +const XX: i8 = 0; +const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0 @@ -219,7 +179,7 @@ static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ 100, // 12 ]; -static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ +const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 0, 0, // 0 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, @@ -260,211 +220,147 @@ static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ 98, // 12 ]; -/// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. -/// -/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), -/// which is an index to the `OL_TO_MDL` lookup table. -#[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Of(pub u32); - -impl Of { - #[inline] - fn clamp_ordinal(ordinal: u32) -> u32 { - if ordinal > 366 { - 0 - } else { - ordinal - } - } - - #[inline] - pub fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); - Of((ordinal << 4) | u32::from(flags)) - } - - #[inline] - pub fn from_mdf(Mdf(mdf): Mdf) -> Of { - let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)), - None => Of(0), - } - } - - #[inline] - pub fn valid(&self) -> bool { - let Of(of) = *self; - let ol = of >> 3; - MIN_OL <= ol && ol <= MAX_OL - } - - #[inline] - pub fn ordinal(&self) -> u32 { - let Of(of) = *self; - of >> 4 - } - - #[inline] - pub fn with_ordinal(&self, ordinal: u32) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); - let Of(of) = *self; - Of((of & 0b1111) | (ordinal << 4)) - } - - #[inline] - pub fn flags(&self) -> YearFlags { - let Of(of) = *self; - YearFlags((of & 0b1111) as u8) - } - - #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { - let Of(of) = *self; - Of((of & !0b1111) | u32::from(flags)) - } - - #[inline] - pub fn weekday(&self) -> Weekday { - let Of(of) = *self; - Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap() - } - - #[inline] - pub fn isoweekdate_raw(&self) -> (u32, Weekday) { - // week ordinal = ordinal + delta - let Of(of) = *self; - let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, Weekday::from_u32(weekord % 7).unwrap()) - } - - #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] - #[inline] - pub fn to_mdf(&self) -> Mdf { - Mdf::from_of(*self) - } - - #[inline] - pub fn succ(&self) -> Of { - let Of(of) = *self; - Of(of + (1 << 4)) - } - - #[inline] - pub fn pred(&self) -> Of { - let Of(of) = *self; - Of(of - (1 << 4)) - } -} - -impl fmt::Debug for Of { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Of(of) = *self; - write!( - f, - "Of(({} << 4) | {:#04o} /*{:?}*/)", - of >> 4, - of & 0b1111, - YearFlags((of & 0b1111) as u8) - ) - } -} - /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` +/// `M_MMMD_DDDD_LFFF` +/// +/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap +/// year flag), which is an index to the `MDL_TO_OL` lookup table. +/// +/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is +/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for +/// efficient lookup. /// -/// The whole bits except for the least 3 bits are referred as `Mdl` -/// (month, day of month and leap flag), -/// which is an index to the `MDL_TO_OL` lookup table. +/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like +/// February 30, can still be represented. This allows the validation to be combined with the final +/// table lookup, which is good for performance. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Mdf(pub u32); +pub(super) struct Mdf(u32); impl Mdf { + /// Makes a new `Mdf` value from month, day and `YearFlags`. + /// + /// This method doesn't fully validate the range of the `month` and `day` parameters, only as + /// much as what can't be deferred until later. The year `flags` are trusted to be correct. + /// + /// # Errors + /// + /// Returns `None` if `month > 12` or `day > 31`. #[inline] - fn clamp_month(month: u32) -> u32 { - if month > 12 { - 0 - } else { - month + pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { + match month <= 12 && day <= 31 { + true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), + false => None, } } + /// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year + /// `flags`. + /// + /// The `ol` is trusted to be valid, and the `flags` are trusted to match it. #[inline] - fn clamp_day(day: u32) -> u32 { - if day > 31 { - 0 - } else { - day - } + pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf { + debug_assert!(ol > 1 && ol <= MAX_OL as i32); + // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value. + Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) } + /// Returns the month of this `Mdf`. #[inline] - pub fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf { - let month = Mdf::clamp_month(month); - let day = Mdf::clamp_day(day); - Mdf((month << 9) | (day << 4) | u32::from(flags)) + pub(super) const fn month(&self) -> u32 { + let Mdf(mdf) = *self; + mdf >> 9 } + /// Replaces the month of this `Mdf`, keeping the day and flags. + /// + /// # Errors + /// + /// Returns `None` if `month > 12`. #[inline] - pub fn from_of(Of(of): Of) -> Mdf { - let ol = of >> 3; - match OL_TO_MDL.get(ol as usize) { - Some(&v) => Mdf(of + (u32::from(v) << 3)), - None => Mdf(0), + pub(super) const fn with_month(&self, month: u32) -> Option { + if month > 12 { + return None; } - } - #[inline] - pub fn valid(&self) -> bool { let Mdf(mdf) = *self; - let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => v >= 0, - None => false, - } + Some(Mdf((mdf & 0b1_1111_1111) | (month << 9))) } + /// Returns the day of this `Mdf`. #[inline] - pub fn month(&self) -> u32 { + pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; - mdf >> 9 + (mdf >> 4) & 0b1_1111 } + /// Replaces the day of this `Mdf`, keeping the month and flags. + /// + /// # Errors + /// + /// Returns `None` if `day > 31`. #[inline] - pub fn with_month(&self, month: u32) -> Mdf { - let month = Mdf::clamp_month(month); - let Mdf(mdf) = *self; - Mdf((mdf & 0b1_1111_1111) | (month << 9)) - } + pub(super) const fn with_day(&self, day: u32) -> Option { + if day > 31 { + return None; + } - #[inline] - pub fn day(&self) -> u32 { let Mdf(mdf) = *self; - (mdf >> 4) & 0b1_1111 + Some(Mdf((mdf & !0b1_1111_0000) | (day << 4))) } + /// Replaces the flags of this `Mdf`, keeping the month and day. #[inline] - pub fn with_day(&self, day: u32) -> Mdf { - let day = Mdf::clamp_day(day); + pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; - Mdf((mdf & !0b1_1111_0000) | (day << 4)) - } - + Mdf((mdf & !0b1111) | flags as u32) + } + + /// Returns the ordinal that corresponds to this `Mdf`. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. #[inline] - pub fn flags(&self) -> YearFlags { - let Mdf(mdf) = *self; - YearFlags((mdf & 0b1111) as u8) + pub(super) const fn ordinal(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some((mdl - v as u8 as u32) >> 1), + } } + /// Returns the year flags of this `Mdf`. #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { - let Mdf(mdf) = *self; - Mdf((mdf & !0b1111) | u32::from(flags)) + pub(super) const fn year_flags(&self) -> YearFlags { + YearFlags((self.0 & 0b1111) as u8) + } + + /// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. + #[inline] + pub(super) const fn ordinal_and_flags(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some(self.0 as i32 - ((v as i32) << 3)), + } } - #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] - #[inline] - pub fn to_of(&self) -> Of { - Of::from_mdf(*self) + #[cfg(test)] + fn valid(&self) -> bool { + let mdl = self.0 >> 3; + MDL_TO_OL[mdl as usize] > 0 } } @@ -484,14 +380,8 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { - #[cfg(test)] - extern crate num_iter; - - use self::num_iter::range_inclusive; - use super::{Mdf, Of}; - use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; - use std::u32; - use Weekday; + use super::Mdf; + use super::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; @@ -532,43 +422,17 @@ mod tests { assert_eq!(GF.nisoweeks(), 52); } - #[test] - fn test_of() { - fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { - for ordinal in range_inclusive(ordinal1, ordinal2) { - let of = Of::new(ordinal, flags); - assert!( - of.valid() == expected, - "ordinal {} = {:?} should be {} for dominical year {:?}", - ordinal, - of, - if expected { "valid" } else { "invalid" }, - flags - ); - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 365); - check(false, flags, 366, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - - for &flags in LEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 366); - check(false, flags, 367, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - } - #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { - for month in range_inclusive(month1, month2) { - for day in range_inclusive(day1, day2) { - let mdf = Mdf::new(month, day, flags); + for month in month1..=month2 { + for day in day1..=day2 { + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None if !expected => continue, + None => panic!("Mdf::new({month}, {day}, {flags:?}) returned None"), + }; + assert!( mdf.valid() == expected, "month {} day {} = {:?} should be {} for dominical year {:?}", @@ -649,76 +513,16 @@ mod tests { } } - #[test] - fn test_of_fields() { - for &flags in FLAGS.iter() { - for ordinal in range_inclusive(1u32, 366) { - let of = Of::new(ordinal, flags); - if of.valid() { - assert_eq!(of.ordinal(), ordinal); - } - } - } - } - - #[test] - fn test_of_with_fields() { - fn check(flags: YearFlags, ordinal: u32) { - let of = Of::new(ordinal, flags); - - for ordinal in range_inclusive(0u32, 1024) { - let of = of.with_ordinal(ordinal); - assert_eq!(of.valid(), Of::new(ordinal, flags).valid()); - if of.valid() { - assert_eq!(of.ordinal(), ordinal); - } - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 365); - } - for &flags in LEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 366); - } - } - - #[test] - fn test_of_weekday() { - assert_eq!(Of::new(1, A).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, B).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, C).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, D).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, E).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, F).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, G).weekday(), Weekday::Mon); - assert_eq!(Of::new(1, AG).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, BA).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, CB).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, DC).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, ED).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, FE).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, GF).weekday(), Weekday::Mon); - - for &flags in FLAGS.iter() { - let mut prev = Of::new(1, flags).weekday(); - for ordinal in range_inclusive(2u32, flags.ndays()) { - let of = Of::new(ordinal, flags); - let expected = prev.succ(); - assert_eq!(of.weekday(), expected); - prev = expected; - } - } - } - #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { - for month in range_inclusive(1u32, 12) { - for day in range_inclusive(1u32, 31) { - let mdf = Mdf::new(month, day, flags); + for month in 1u32..=12 { + for day in 1u32..31 { + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None => continue, + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -731,20 +535,28 @@ mod tests { #[test] fn test_mdf_with_fields() { fn check(flags: YearFlags, month: u32, day: u32) { - let mdf = Mdf::new(month, day, flags); + let mdf = Mdf::new(month, day, flags).unwrap(); + + for month in 0u32..=16 { + let mdf = match mdf.with_month(month) { + Some(mdf) => mdf, + None if month > 12 => continue, + None => panic!("failed to create Mdf with month {month}"), + }; - for month in range_inclusive(0u32, 16) { - let mdf = mdf.with_month(month); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); } } - for day in range_inclusive(0u32, 1024) { - let mdf = mdf.with_day(day); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + for day in 0u32..=1024 { + let mdf = match mdf.with_day(day) { + Some(mdf) => mdf, + None if day > 31 => continue, + None => panic!("failed to create Mdf with month {month}"), + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -771,47 +583,9 @@ mod tests { } #[test] - fn test_of_isoweekdate_raw() { - for &flags in FLAGS.iter() { - // January 4 should be in the first week - let (week, _) = Of::new(4 /* January 4 */, flags).isoweekdate_raw(); - assert_eq!(week, 1); - } - } - - #[test] - fn test_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { - let of = Of(i); - assert_eq!(of.valid(), of.to_mdf().valid()); - } - } - - #[test] - fn test_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { - let mdf = Mdf(i); - assert_eq!(mdf.valid(), mdf.to_of().valid()); - } - } - - #[test] - fn test_of_to_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { - let of = Of(i); - if of.valid() { - assert_eq!(of, of.to_mdf().to_of()); - } - } - } - - #[test] - fn test_mdf_to_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { - let mdf = Mdf(i); - if mdf.valid() { - assert_eq!(mdf, mdf.to_of().to_mdf()); - } - } + fn test_mdf_new_range() { + let flags = YearFlags::from_year(2023); + assert!(Mdf::new(13, 1, flags).is_none()); + assert!(Mdf::new(1, 32, flags).is_none()); } } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 7e34313bad..25fcbc138e 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -5,55 +5,66 @@ use core::fmt; -use super::internals::{DateImpl, Of, YearFlags}; +use super::internals::YearFlags; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 week. /// /// This type, combined with [`Weekday`](../enum.Weekday.html), -/// constitues the ISO 8601 [week date](./struct.NaiveDate.html#week-date). +/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct IsoWeek { - // note that this allows for larger year range than `NaiveDate`. - // this is crucial because we have an edge case for the first and last week supported, + // Note that this allows for larger year range than `NaiveDate`. + // This is crucial because we have an edge case for the first and last week supported, // which year number might not match the calendar year number. - ywf: DateImpl, // (year << 10) | (week << 4) | flag + ywf: i32, // (year << 10) | (week << 4) | flag } -/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. -// -// internal use only. we don't expose the public constructor for `IsoWeek` for now, -// because the year range for the week date and the calendar date do not match and -// it is confusing to have a date that is out of range in one and not in another. -// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. -pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { - let (rawweek, _) = of.isoweekdate_raw(); - let (year, week) = if rawweek < 1 { - // previous year - let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); - (year - 1, prevlastweek) - } else { - let lastweek = of.flags().nisoweeks(); - if rawweek > lastweek { - // next year - (year + 1, 1) +impl IsoWeek { + /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. + // + // Internal use only. We don't expose the public constructor for `IsoWeek` for now + // because the year range for the week date and the calendar date do not match, and + // it is confusing to have a date that is out of range in one and not in another. + // Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { + let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; + let (year, week) = if rawweek < 1 { + // previous year + let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek) } else { - (year, rawweek) - } - }; - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) } -} + let lastweek = year_flags.nisoweeks(); + if rawweek > lastweek { + // next year + (year + 1, 1) + } else { + (year, rawweek) + } + }; + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } + } -impl IsoWeek { /// Returns the year number for this ISO week. /// /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().year(), 2015); /// ``` /// @@ -62,12 +73,12 @@ impl IsoWeek { /// /// ``` /// # use chrono::{NaiveDate, Datelike, Weekday}; - /// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.year(), 2014); - /// assert_eq!(d, NaiveDate::from_ymd(2014, 12, 29)); + /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` #[inline] - pub fn year(&self) -> i32 { + pub const fn year(&self) -> i32 { self.ywf >> 10 } @@ -78,13 +89,13 @@ impl IsoWeek { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week(), 15); /// ``` #[inline] - pub fn week(&self) -> u32 { + pub const fn week(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 } @@ -95,13 +106,13 @@ impl IsoWeek { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week0(), 14); /// ``` #[inline] - pub fn week0(&self) -> u32 { + pub const fn week0(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 - 1 } } @@ -113,51 +124,125 @@ impl IsoWeek { /// # Example /// /// ``` -/// use chrono::{NaiveDate, Datelike}; +/// use chrono::{Datelike, NaiveDate}; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5).iso_week()), "2015-W36"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 3).iso_week()), "0000-W01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31).iso_week()), "9999-W52"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), +/// "2015-W36" +/// ); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), +/// "9999-W52" +/// ); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::{NaiveDate, Datelike}; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 2).iso_week()), "-0001-W52"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31).iso_week()), "+10000-W52"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), +/// "+10000-W52" +/// ); /// ``` impl fmt::Debug for IsoWeek { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let year = self.year(); let week = self.week(); - if 0 <= year && year <= 9999 { - write!(f, "{:04}-W{:02}", year, week) + if (0..=9999).contains(&year) { + write!(f, "{year:04}-W{week:02}") } else { // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}-W{:02}", year, week) + write!(f, "{year:+05}-W{week:02}") + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for IsoWeek { + fn format(&self, fmt: defmt::Formatter) { + let year = self.year(); + let week = self.week(); + if (0..=9999).contains(&year) { + defmt::write!(fmt, "{:04}-W{:02}", year, week) + } else { + // ISO 8601 requires the explicit sign for out-of-range years + let sign = ['+', '-'][(year < 0) as usize]; + defmt::write!(fmt, "{}{:05}-W{:02}", sign, year.abs(), week) } } } #[cfg(test)] mod tests { - use naive::{internals, MAX_DATE, MIN_DATE}; - use Datelike; + #[cfg(feature = "rkyv-validation")] + use super::IsoWeek; + use crate::Datelike; + use crate::naive::date::{self, NaiveDate}; #[test] fn test_iso_week_extremes() { - let minweek = MIN_DATE.iso_week(); - let maxweek = MAX_DATE.iso_week(); + let minweek = NaiveDate::MIN.iso_week(); + let maxweek = NaiveDate::MAX.iso_week(); - assert_eq!(minweek.year(), internals::MIN_YEAR); + assert_eq!(minweek.year(), date::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); - assert_eq!(format!("{:?}", minweek), MIN_DATE.format("%G-W%V").to_string()); + #[cfg(feature = "alloc")] + assert_eq!(format!("{minweek:?}"), NaiveDate::MIN.format("%G-W%V").to_string()); - assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); + assert_eq!(maxweek.year(), date::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); - assert_eq!(format!("{:?}", maxweek), MAX_DATE.format("%G-W%V").to_string()); + #[cfg(feature = "alloc")] + assert_eq!(format!("{maxweek:?}"), NaiveDate::MAX.format("%G-W%V").to_string()); + } + + #[test] + fn test_iso_week_equivalence_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_equivalence_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let minweek = NaiveDate::MIN.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), minweek); + + let maxweek = NaiveDate::MAX.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), maxweek); } } diff --git a/src/naive/mod.rs b/src/naive/mod.rs new file mode 100644 index 0000000000..4cc205595c --- /dev/null +++ b/src/naive/mod.rs @@ -0,0 +1,340 @@ +//! Date and time types unconcerned with timezones. +//! +//! They are primarily building blocks for other types +//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), +//! but can be also used for the simpler date and time handling. + +use core::hash::{Hash, Hasher}; +use core::ops::RangeInclusive; + +use crate::Weekday; +use crate::expect; + +pub(crate) mod date; +pub(crate) mod datetime; +mod internals; +pub(crate) mod isoweek; +pub(crate) mod time; + +#[allow(deprecated)] +pub use self::date::{MAX_DATE, MIN_DATE}; +pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; +#[allow(deprecated)] +pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime}; +pub use self::isoweek::IsoWeek; +pub use self::time::NaiveTime; + +#[cfg(feature = "__internal_bench")] +#[doc(hidden)] +pub use self::internals::YearFlags as __BenchYearFlags; + +/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first +/// day of the week. +#[derive(Clone, Copy, Debug, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NaiveWeek { + date: NaiveDate, + start: Weekday, +} + +impl NaiveWeek { + /// Create a new `NaiveWeek` + pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { + Self { date, start } + } + + /// Returns a date representing the first day of the week. + /// + /// # Panics + /// + /// Panics if the first day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.first_day() <= date); + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub const fn first_day(&self) -> NaiveDate { + expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the first day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MIN; + /// let week = date.week(Weekday::Mon); + /// if let Some(first_day) = week.checked_first_day() { + /// assert!(first_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_first_day(&self) -> Option { + let start = self.start.num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to subtract from `self.date`. + // Do not construct an intermediate date beyond `self.date`, because that may be out of + // range if `date` is close to `NaiveDate::MAX`. + let days = start - ref_day - if start > ref_day { 7 } else { 0 }; + self.date.add_days(days) + } + + /// Returns a date representing the last day of the week. + /// + /// # Panics + /// + /// Panics if the last day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.last_day() >= date); + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub const fn last_day(&self) -> NaiveDate { + expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the last day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// if let Some(last_day) = week.checked_last_day() { + /// assert!(last_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_last_day(&self) -> Option { + let end = self.start.pred().num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to add to `self.date`. + // Do not construct an intermediate date before `self.date` (like with `first_day()`), + // because that may be out of range if `date` is close to `NaiveDate::MIN`. + let days = end - ref_day + if end < ref_day { 7 } else { 0 }; + self.date.add_days(days) + } + + /// Returns a [`RangeInclusive`] representing the whole week bounded by + /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions. + /// + /// # Panics + /// + /// Panics if the either the first or last day of the week happens to fall just out of range of + /// `NaiveDate` (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// let days = week.days(); + /// assert!(days.contains(&date)); + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub const fn days(&self) -> RangeInclusive { + // `expect` doesn't work because `RangeInclusive` is not `Copy` + match self.checked_days() { + Some(val) => val, + None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"), + } + } + + /// Returns an [`Option>`] representing the whole week bounded by + /// [checked_first_day](NaiveWeek::checked_first_day) and + /// [checked_last_day](NaiveWeek::checked_last_day) functions. + /// + /// Returns `None` if either of the boundaries are out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// let _days = match week.checked_days() { + /// Some(d) => d, + /// None => { + /// // error handling code + /// return; + /// } + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_days(&self) -> Option> { + match (self.checked_first_day(), self.checked_last_day()) { + (Some(first), Some(last)) => Some(first..=last), + (_, _) => None, + } + } +} + +impl PartialEq for NaiveWeek { + fn eq(&self, other: &Self) -> bool { + self.first_day() == other.first_day() + } +} + +impl Hash for NaiveWeek { + fn hash(&self, state: &mut H) { + self.first_day().hash(state); + } +} + +/// A duration in calendar days. +/// +/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` +/// doesn't increment the day value as expected due to it being a fixed number of seconds. This +/// difference applies only when dealing with `DateTime` data types and in other cases +/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Days(pub(crate) u64); + +impl Days { + /// Construct a new `Days` from a number of days + pub const fn new(num: u64) -> Self { + Self(num) + } +} + +/// Serialization/Deserialization of `NaiveDateTime` in alternate formats +/// +/// The various modules in here are intended to be used with serde's [`with` annotation] to +/// serialize as something other than the default ISO 8601 format. +/// +/// [`with` annotation]: https://serde.rs/field-attrs.html#with +#[cfg(feature = "serde")] +pub mod serde { + pub use super::datetime::serde::*; +} + +#[cfg(test)] +mod test { + use crate::{NaiveDate, NaiveWeek, Weekday}; + use std::hash::{DefaultHasher, Hash, Hasher}; + #[test] + fn test_naiveweek() { + let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); + let asserts = [ + (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), + (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), + (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), + (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), + (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), + (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), + (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); + assert!(days.contains(&date)); + } + } + + #[test] + fn test_naiveweek_min_max() { + let date_max = NaiveDate::MAX; + assert!(date_max.week(Weekday::Mon).first_day() <= date_max); + let date_min = NaiveDate::MIN; + assert!(date_min.week(Weekday::Mon).last_day() >= date_min); + } + + #[test] + fn test_naiveweek_checked_no_panic() { + let date_max = NaiveDate::MAX; + if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() { + assert!(last == date_max); + } + let date_min = NaiveDate::MIN; + if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() { + assert!(first == date_min); + } + let _ = date_min.week(Weekday::Mon).checked_days(); + let _ = date_max.week(Weekday::Mon).checked_days(); + } + + #[test] + fn test_naiveweek_eq() { + let a = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon }; + let b = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; + assert_eq!(a, b); + + let c = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; + assert_ne!(a, c); + assert_ne!(b, c); + } + + #[test] + fn test_naiveweek_hash() { + let a = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon }; + let b = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; + let c = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; + + let mut hasher = DefaultHasher::default(); + a.hash(&mut hasher); + let a_hash = hasher.finish(); + + hasher = DefaultHasher::default(); + b.hash(&mut hasher); + let b_hash = hasher.finish(); + + hasher = DefaultHasher::default(); + c.hash(&mut hasher); + let c_hash = hasher.finish(); + + assert_eq!(a_hash, b_hash); + assert_ne!(b_hash, c_hash); + assert_ne!(a_hash, c_hash); + } +} diff --git a/src/naive/time.rs b/src/naive/time.rs deleted file mode 100644 index 43f4dd25af..0000000000 --- a/src/naive/time.rs +++ /dev/null @@ -1,1830 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 time without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use Timelike; - -pub const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; -pub const MAX_TIME: NaiveTime = NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; - -/// ISO 8601 time without timezone. -/// Allows for the nanosecond precision and optional leap second representation. -/// -/// # Leap Second Handling -/// -/// Since 1960s, the manmade atomic clock has been so accurate that -/// it is much more accurate than Earth's own motion. -/// It became desirable to define the civil time in terms of the atomic clock, -/// but that risks the desynchronization of the civil time from Earth. -/// To account for this, the designers of the Coordinated Universal Time (UTC) -/// made that the UTC should be kept within 0.9 seconds of the observed Earth-bound time. -/// When the mean solar day is longer than the ideal (86,400 seconds), -/// the error slowly accumulates and it is necessary to add a **leap second** -/// to slow the UTC down a bit. -/// (We may also remove a second to speed the UTC up a bit, but it never happened.) -/// The leap second, if any, follows 23:59:59 of June 30 or December 31 in the UTC. -/// -/// Fast forward to the 21st century, -/// we have seen 26 leap seconds from January 1972 to December 2015. -/// Yes, 26 seconds. Probably you can read this paragraph within 26 seconds. -/// But those 26 seconds, and possibly more in the future, are never predictable, -/// and whether to add a leap second or not is known only before 6 months. -/// Internet-based clocks (via NTP) do account for known leap seconds, -/// but the system API normally doesn't (and often can't, with no network connection) -/// and there is no reliable way to retrieve leap second information. -/// -/// Chrono does not try to accurately implement leap seconds; it is impossible. -/// Rather, **it allows for leap seconds but behaves as if there are *no other* leap seconds.** -/// Various operations will ignore any possible leap second(s) -/// except when any of the operands were actually leap seconds. -/// -/// If you cannot tolerate this behavior, -/// you must use a separate `TimeZone` for the International Atomic Time (TAI). -/// TAI is like UTC but has no leap seconds, and thus slightly differs from UTC. -/// Chrono does not yet provide such implementation, but it is planned. -/// -/// ## Representing Leap Seconds -/// -/// The leap second is indicated via fractional seconds more than 1 second. -/// This makes possible to treat a leap second as the prior non-leap second -/// if you don't care about sub-second accuracy. -/// You should use the proper formatting to get the raw leap second. -/// -/// All methods accepting fractional seconds will accept such values. -/// -/// ``` -/// use chrono::{NaiveDate, NaiveTime, Utc, TimeZone}; -/// -/// let t = NaiveTime::from_hms_milli(8, 59, 59, 1_000); -/// -/// let dt1 = NaiveDate::from_ymd(2015, 7, 1).and_hms_micro(8, 59, 59, 1_000_000); -/// -/// let dt2 = Utc.ymd(2015, 6, 30).and_hms_nano(23, 59, 59, 1_000_000_000); -/// # let _ = (t, dt1, dt2); -/// ``` -/// -/// Note that the leap second can happen anytime given an appropriate time zone; -/// 2015-07-01 01:23:60 would be a proper leap second if UTC+01:24 had existed. -/// Practically speaking, though, by the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment. -/// -/// ## Date And Time Arithmetics -/// -/// As a concrete example, let's assume that `03:00:60` and `04:00:60` are leap seconds. -/// In reality, of course, leap seconds are separated by at least 6 months. -/// We will also use some intuitive concise notations for the explanation. -/// -/// `Time + Duration` -/// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): -/// -/// - `03:00:00 + 1s = 03:00:01`. -/// - `03:00:59 + 60s = 03:02:00`. -/// - `03:00:59 + 1s = 03:01:00`. -/// - `03:00:60 + 1s = 03:01:00`. -/// Note that the sum is identical to the previous. -/// - `03:00:60 + 60s = 03:01:59`. -/// - `03:00:60 + 61s = 03:02:00`. -/// - `03:00:60.1 + 0.8s = 03:00:60.9`. -/// -/// `Time - Duration` -/// (short for [`NaiveTime::overflowing_sub_signed`](#method.overflowing_sub_signed)): -/// -/// - `03:00:00 - 1s = 02:59:59`. -/// - `03:01:00 - 1s = 03:00:59`. -/// - `03:01:00 - 60s = 03:00:00`. -/// - `03:00:60 - 60s = 03:00:00`. -/// Note that the result is identical to the previous. -/// - `03:00:60.7 - 0.4s = 03:00:60.3`. -/// - `03:00:60.7 - 0.9s = 03:00:59.8`. -/// -/// `Time - Time` -/// (short for [`NaiveTime::signed_duration_since`](#method.signed_duration_since)): -/// -/// - `04:00:00 - 03:00:00 = 3600s`. -/// - `03:01:00 - 03:00:00 = 60s`. -/// - `03:00:60 - 03:00:00 = 60s`. -/// Note that the difference is identical to the previous. -/// - `03:00:60.6 - 03:00:59.4 = 1.2s`. -/// - `03:01:00 - 03:00:59.8 = 0.2s`. -/// - `03:01:00 - 03:00:60.5 = 0.5s`. -/// Note that the difference is larger than the previous, -/// even though the leap second clearly follows the previous whole second. -/// - `04:00:60.9 - 03:00:60.1 = -/// (04:00:60.9 - 04:00:00) + (04:00:00 - 03:01:00) + (03:01:00 - 03:00:60.1) = -/// 60.9s + 3540s + 0.9s = 3601.8s`. -/// -/// In general, -/// -/// - `Time + Duration` unconditionally equals to `Duration + Time`. -/// -/// - `Time - Duration` unconditionally equals to `Time + (-Duration)`. -/// -/// - `Time1 - Time2` unconditionally equals to `-(Time2 - Time1)`. -/// -/// - Associativity does not generally hold, because -/// `(Time + Duration1) - Duration2` no longer equals to `Time + (Duration1 - Duration2)` -/// for two positive durations. -/// -/// - As a special case, `(Time + Duration) - Duration` also does not equal to `Time`. -/// -/// - If you can assume that all durations have the same sign, however, -/// then the associativity holds: -/// `(Time + Duration1) + Duration2` equals to `Time + (Duration1 + Duration2)` -/// for two positive durations. -/// -/// ## Reading And Writing Leap Seconds -/// -/// The "typical" leap seconds on the minute boundary are -/// correctly handled both in the formatting and parsing. -/// The leap second in the human-readable representation -/// will be represented as the second part being 60, as required by ISO 8601. -/// -/// ``` -/// use chrono::{Utc, TimeZone}; -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_000); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); -/// ``` -/// -/// There are hypothetical leap seconds not on the minute boundary -/// nevertheless supported by Chrono. -/// They are allowed for the sake of completeness and consistency; -/// there were several "exotic" time zone offsets with fractional minutes prior to UTC after all. -/// For such cases the human-readable representation is ambiguous -/// and would be read back to the next non-leap second. -/// -/// ``` -/// use chrono::{DateTime, Utc, TimeZone}; -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 56, 4, 1_000); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms(23, 56, 5); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); -/// ``` -/// -/// Since Chrono alone cannot determine any existence of leap seconds, -/// **there is absolutely no guarantee that the leap second read has actually happened**. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveTime { - secs: u32, - frac: u32, -} - -impl NaiveTime { - /// Makes a new `NaiveTime` from hour, minute and second. - /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. - /// - /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 0); - /// ``` - #[inline] - pub fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { - NaiveTime::from_hms_opt(hour, min, sec).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute and second. - /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead. - /// - /// Returns `None` on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let from_hms_opt = NaiveTime::from_hms_opt; - /// - /// assert!(from_hms_opt(0, 0, 0).is_some()); - /// assert!(from_hms_opt(23, 59, 59).is_some()); - /// assert!(from_hms_opt(24, 0, 0).is_none()); - /// assert!(from_hms_opt(23, 60, 0).is_none()); - /// assert!(from_hms_opt(23, 59, 60).is_none()); - /// ``` - #[inline] - pub fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { - NaiveTime::from_hms_nano_opt(hour, min, sec, 0) - } - - /// Makes a new `NaiveTime` from hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_milli(23, 56, 4, 12); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_000_000); - /// ``` - #[inline] - pub fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let from_hmsm_opt = NaiveTime::from_hms_milli_opt; - /// - /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); - /// ``` - #[inline] - pub fn from_hms_milli_opt(hour: u32, min: u32, sec: u32, milli: u32) -> Option { - milli - .checked_mul(1_000_000) - .and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) - } - - /// Makes a new `NaiveTime` from hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_micro(23, 56, 4, 12_345); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_000); - /// ``` - #[inline] - pub fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let from_hmsu_opt = NaiveTime::from_hms_micro_opt; - /// - /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); - /// ``` - #[inline] - pub fn from_hms_micro_opt(hour: u32, min: u32, sec: u32, micro: u32) -> Option { - micro.checked_mul(1_000).and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) - } - - /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ``` - #[inline] - pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let from_hmsn_opt = NaiveTime::from_hms_nano_opt; - /// - /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); - /// ``` - #[inline] - pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { - if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { - return None; - } - let secs = hour * 3600 + min * 60 + sec; - Some(NaiveTime { secs: secs, frac: nano }) - } - - /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid number of seconds and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_num_seconds_from_midnight(86164, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ``` - #[inline] - pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { - NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") - } - - /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid number of seconds and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight_opt; - /// - /// assert!(from_nsecs_opt(0, 0).is_some()); - /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); - /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_nsecs_opt(86_400, 0).is_none()); - /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); - /// ``` - #[inline] - pub fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { - if secs >= 86_400 || nano >= 2_000_000_000 { - return None; - } - Some(NaiveTime { secs: secs, frac: nano }) - } - - /// Parses a string with the specified format string and returns a new `NaiveTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let parse_from_str = NaiveTime::parse_from_str; - /// - /// assert_eq!(parse_from_str("23:56:04", "%H:%M:%S"), - /// Ok(NaiveTime::from_hms(23, 56, 4))); - /// assert_eq!(parse_from_str("pm012345.6789", "%p%I%M%S%.f"), - /// Ok(NaiveTime::from_hms_micro(13, 23, 45, 678_900))); - /// ``` - /// - /// Date and offset is ignored for the purpose of parsing. - /// - /// ``` - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveTime::from_hms(12, 34, 56))); - /// ``` - /// - /// [Leap seconds](#leap-second-handling) are correctly handled by - /// treating any time of the form `hh:mm:60` as a leap second. - /// (This equally applies to the formatting, so the round trip is possible.) - /// - /// ``` - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("08:59:60.123", "%H:%M:%S%.f"), - /// Ok(NaiveTime::from_hms_milli(8, 59, 59, 1_123))); - /// ``` - /// - /// Missing seconds are assumed to be zero, - /// but out-of-bound times or insufficient fields are errors otherwise. - /// - /// ``` - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("7:15", "%H:%M"), - /// Ok(NaiveTime::from_hms(7, 15, 0))); - /// - /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); - /// assert!(parse_from_str("12", "%H").is_err()); - /// assert!(parse_from_str("17:60", "%H:%M").is_err()); - /// assert!(parse_from_str("24:00:00", "%H:%M:%S").is_err()); - /// ``` - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// Here `%H` is for 24-hour clocks, unlike `%I`, - /// and thus can be independently determined without AM/PM. - /// - /// ``` - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert!(parse_from_str("13:07 AM", "%H:%M %p").is_err()); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_time() - } - - /// Adds given `Duration` to the current time, - /// and also returns the number of *seconds* - /// in the integral number of days ignored from the addition. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hms = NaiveTime::from_hms; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(11)), - /// (from_hms(14, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(23)), - /// (from_hms(2, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), - /// (from_hms(20, 4, 5), -86_400)); - /// # } - /// ``` - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { - let mut secs = self.secs; - let mut frac = self.frac; - - // check if `self` is a leap second and adding `rhs` would escape that leap second. - // if it's the case, update `self` and `rhs` to involve no leap second; - // otherwise the addition immediately finishes. - if frac >= 1_000_000_000 { - let rfrac = 2_000_000_000 - frac; - if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs = rhs - OldDuration::nanoseconds(i64::from(rfrac)); - secs += 1; - frac = 0; - } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs = rhs + OldDuration::nanoseconds(i64::from(frac)); - frac = 0; - } else { - frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; - debug_assert!(frac < 2_000_000_000); - return (NaiveTime { secs: secs, frac: frac }, 0); - } - } - debug_assert!(secs <= 86_400); - debug_assert!(frac < 1_000_000_000); - - let rhssecs = rhs.num_seconds(); - let rhsfrac = (rhs - OldDuration::seconds(rhssecs)).num_nanoseconds().unwrap(); - debug_assert_eq!(OldDuration::seconds(rhssecs) + OldDuration::nanoseconds(rhsfrac), rhs); - let rhssecsinday = rhssecs % 86_400; - let mut morerhssecs = rhssecs - rhssecsinday; - let rhssecs = rhssecsinday as i32; - let rhsfrac = rhsfrac as i32; - debug_assert!(-86_400 < rhssecs && rhssecs < 86_400); - debug_assert_eq!(morerhssecs % 86_400, 0); - debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); - - let mut secs = secs as i32 + rhssecs; - let mut frac = frac as i32 + rhsfrac; - debug_assert!(-86_400 < secs && secs < 2 * 86_400); - debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); - - if frac < 0 { - frac += 1_000_000_000; - secs -= 1; - } else if frac >= 1_000_000_000 { - frac -= 1_000_000_000; - secs += 1; - } - debug_assert!(-86_400 <= secs && secs < 2 * 86_400); - debug_assert!(0 <= frac && frac < 1_000_000_000); - - if secs < 0 { - secs += 86_400; - morerhssecs -= 86_400; - } else if secs >= 86_400 { - secs -= 86_400; - morerhssecs += 86_400; - } - debug_assert!(0 <= secs && secs < 86_400); - - (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) - } - - /// Subtracts given `Duration` from the current time, - /// and also returns the number of *seconds* - /// in the integral number of days ignored from the subtraction. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hms = NaiveTime::from_hms; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(2)), - /// (from_hms(1, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(17)), - /// (from_hms(10, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(-22)), - /// (from_hms(1, 4, 5), -86_400)); - /// # } - /// ``` - #[inline] - pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { - let (time, rhs) = self.overflowing_add_signed(-rhs); - (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) - } - - /// Subtracts another `NaiveTime` from the current time. - /// Returns a `Duration` within +/- 1 day. - /// This does not overflow or underflow at all. - /// - /// As a part of Chrono's [leap second handling](#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when any of the `NaiveTime`s themselves represents a leap second - /// in which case the assumption becomes that - /// **there are exactly one (or two) leap second(s) ever**. - /// - /// # Example - /// - /// ``` - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hmsm = NaiveTime::from_hms_milli; - /// let since = NaiveTime::signed_duration_since; - /// - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), - /// Duration::zero()); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), - /// Duration::milliseconds(25)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), - /// Duration::milliseconds(975)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), - /// Duration::seconds(7)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), - /// Duration::seconds(5 * 60)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), - /// Duration::seconds(3 * 3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), - /// Duration::seconds(-3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); - /// # } - /// ``` - /// - /// Leap seconds are handled, but the subtraction assumes that - /// there were no other leap seconds happened. - /// - /// ``` - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveTime}; - /// # let from_hmsm = NaiveTime::from_hms_milli; - /// # let since = NaiveTime::signed_duration_since; - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), - /// Duration::seconds(1)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), - /// Duration::milliseconds(1500)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), - /// Duration::seconds(60)); - /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(1)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(61)); - /// # } - /// ``` - pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { - // | | :leap| | | | | | | :leap| | - // | | : | | | | | | | : | | - // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- - // | `rhs` | | `self` - // |======================================>| | - // | | `self.secs - rhs.secs` |`self.frac` - // |====>| | |======>| - // `rhs.frac`|========================================>| - // | | | `self - rhs` | | - - use core::cmp::Ordering; - - let secs = i64::from(self.secs) - i64::from(rhs.secs); - let frac = i64::from(self.frac) - i64::from(rhs.frac); - - // `secs` may contain a leap second yet to be counted - let adjust = match self.secs.cmp(&rhs.secs) { - Ordering::Greater => { - if rhs.frac >= 1_000_000_000 { - 1 - } else { - 0 - } - } - Ordering::Equal => 0, - Ordering::Less => { - if self.frac >= 1_000_000_000 { - -1 - } else { - 0 - } - } - }; - - OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) - } - - /// Formats the time with the specified formatting items. - /// Otherwise it is the same as the ordinary [`format`](#method.format) method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%H:%M:%S"); - /// let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveTime; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); - /// # let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(None, Some(*self), items) - } - - /// Formats the time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveTime; - /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); - /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); - /// ``` - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ``` - /// # use chrono::NaiveTime; - /// # let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); - /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); - /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Returns a triple of the hour, minute and second numbers. - fn hms(&self) -> (u32, u32, u32) { - let (mins, sec) = div_mod_floor(self.secs, 60); - let (hour, min) = div_mod_floor(mins, 60); - (hour, min, sec) - } -} - -impl Timelike for NaiveTime { - /// Returns the hour number from 0 to 23. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).hour(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).hour(), 23); - /// ``` - #[inline] - fn hour(&self) -> u32 { - self.hms().0 - } - - /// Returns the minute number from 0 to 59. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).minute(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).minute(), 56); - /// ``` - #[inline] - fn minute(&self) -> u32 { - self.hms().1 - } - - /// Returns the second number from 0 to 59. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).second(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).second(), 4); - /// ``` - /// - /// This method never returns 60 even when it is a leap second. - /// ([Why?](#leap-second-handling)) - /// Use the proper [formatting method](#method.format) to get a human-readable representation. - /// - /// ``` - /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); - /// assert_eq!(leap.second(), 59); - /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); - /// ``` - #[inline] - fn second(&self) -> u32 { - self.hms().2 - } - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](#leap-second-handling). - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).nanosecond(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).nanosecond(), 12_345_678); - /// ``` - /// - /// Leap seconds may have seemingly out-of-range return values. - /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or - /// use the proper [formatting method](#method.format) to get a human-readable representation. - /// - /// ``` - /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); - /// assert_eq!(leap.nanosecond(), 1_000_000_000); - /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); - /// ``` - #[inline] - fn nanosecond(&self) -> u32 { - self.frac - } - - /// Makes a new `NaiveTime` with the hour number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano(7, 56, 4, 12_345_678))); - /// assert_eq!(dt.with_hour(24), None); - /// ``` - #[inline] - fn with_hour(&self, hour: u32) -> Option { - if hour >= 24 { - return None; - } - let secs = hour * 3600 + self.secs % 3600; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with the minute number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano(23, 45, 4, 12_345_678))); - /// assert_eq!(dt.with_minute(60), None); - /// ``` - #[inline] - fn with_minute(&self, min: u32) -> Option { - if min >= 60 { - return None; - } - let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with the second number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// As with the [`second`](#method.second) method, - /// the input range is restricted to 0 through 59. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano(23, 56, 17, 12_345_678))); - /// assert_eq!(dt.with_second(60), None); - /// ``` - #[inline] - fn with_second(&self, sec: u32) -> Option { - if sec >= 60 { - return None; - } - let secs = self.secs / 60 * 60 + sec; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// As with the [`nanosecond`](#method.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 333_333_333))); - /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); - /// ``` - /// - /// Leap seconds can theoretically follow *any* whole second. - /// The following would be a proper leap second at the time zone offset of UTC-00:03:57 - /// (there are several historical examples comparable to this "non-sense" offset), - /// and therefore is allowed. - /// - /// ``` - /// # use chrono::{NaiveTime, Timelike}; - /// # let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 1_333_333_333))); - /// ``` - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option { - if nano >= 2_000_000_000 { - return None; - } - Some(NaiveTime { frac: nano, ..*self }) - } - - /// Returns the number of non-leap seconds past the last midnight. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(1, 2, 3).num_seconds_from_midnight(), - /// 3723); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).num_seconds_from_midnight(), - /// 86164); - /// assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).num_seconds_from_midnight(), - /// 86399); - /// ``` - #[inline] - fn num_seconds_from_midnight(&self) -> u32 { - self.secs // do not repeat the calculation! - } -} - -/// `NaiveTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveTime { - fn hash(&self, state: &mut H) { - self.secs.hash(state); - self.frac.hash(state); - } -} - -/// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(1), from_hmsm(3, 5, 8, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::milliseconds(80), from_hmsm(3, 5, 7, 80)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(280), from_hmsm(3, 5, 8, 230)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(-980), from_hmsm(3, 5, 6, 970)); -/// # } -/// ``` -/// -/// The addition wraps around. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } -/// ``` -/// -/// Leap seconds are handled, but the addition assumes that it is the only leap second happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), from_hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), from_hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), from_hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), from_hmsm(3, 5, 59, 300)); -/// # } -/// ``` -impl Add for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveTime { - self.overflowing_add_signed(rhs).0 - } -} - -impl AddAssign for NaiveTime { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// It is the same as the addition with a negated `Duration`. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::milliseconds(80), from_hmsm(3, 5, 6, 920)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) - Duration::milliseconds(280), from_hmsm(3, 5, 7, 670)); -/// # } -/// ``` -/// -/// The subtraction wraps around. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } -/// ``` -/// -/// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), from_hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), from_hmsm(3, 6, 0, 300)); -/// # } -/// ``` -impl Sub for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveTime { - self.overflowing_sub_signed(rhs).0 - } -} - -impl SubAssign for NaiveTime { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveTime` from the current time. -/// Returns a `Duration` within +/- 1 day. -/// This does not overflow or underflow at all. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the subtraction assumes that **there is no leap second ever**, -/// except when any of the `NaiveTime`s themselves represents a leap second -/// in which case the assumption becomes that -/// **there are exactly one (or two) leap second(s) ever**. -/// -/// The implementation is a wrapper around -/// [`NaiveTime::signed_duration_since`](#method.signed_duration_since). -/// -/// # Example -/// -/// ``` -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), Duration::zero()); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), Duration::milliseconds(25)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), Duration::milliseconds(975)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), Duration::seconds(7)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), Duration::seconds(5 * 60)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), Duration::seconds(3 * 3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), Duration::seconds(-3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); -/// # } -/// ``` -/// -/// Leap seconds are handled, but the subtraction assumes that -/// there were no other leap seconds happened. -/// -/// ``` -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), -/// Duration::milliseconds(1500)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), Duration::seconds(60)); -/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), Duration::seconds(1)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), -/// Duration::seconds(61)); -/// # } -/// ``` -impl Sub for NaiveTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// The `Debug` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveTime; -/// -/// assert_eq!(format!("{:?}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); -/// ``` -/// -/// Leap seconds may also be used. -/// -/// ``` -/// # use chrono::NaiveTime; -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); -/// ``` -impl fmt::Debug for NaiveTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (hour, min, sec) = self.hms(); - let (sec, nano) = if self.frac >= 1_000_000_000 { - (sec + 1, self.frac - 1_000_000_000) - } else { - (sec, self.frac) - }; - - write!(f, "{:02}:{:02}:{:02}", hour, min, sec)?; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(f, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(f, ".{:06}", nano / 1_000) - } else { - write!(f, ".{:09}", nano) - } - } -} - -/// The `Display` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveTime; -/// -/// assert_eq!(format!("{}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); -/// ``` -/// -/// Leap seconds may also be used. -/// -/// ``` -/// # use chrono::NaiveTime; -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); -/// ``` -impl fmt::Display for NaiveTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -/// Parsing a `str` into a `NaiveTime` uses the same format, -/// [`%H:%M:%S%.f`](../format/strftime/index.html), as in `Debug` and `Display`. -/// -/// # Example -/// -/// ``` -/// use chrono::NaiveTime; -/// -/// let t = NaiveTime::from_hms(23, 56, 4); -/// assert_eq!("23:56:04".parse::(), Ok(t)); -/// -/// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); -/// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); -/// -/// let t = NaiveTime::from_hms_nano(23, 59, 59, 1_234_567_890); // leap second -/// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); -/// -/// assert!("foo".parse::().is_err()); -/// ``` -impl str::FromStr for NaiveTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_time() - } -} - -/// The default value for a NaiveTime is midnight, 00:00:00 exactly. -/// -/// # Example -/// -/// ```rust -/// use chrono::NaiveTime; -/// -/// let default_time = NaiveTime::default(); -/// assert_eq!(default_time, NaiveTime::from_hms(0, 0, 0)); -/// ``` -impl Default for NaiveTime { - fn default() -> Self { - NaiveTime::from_hms(0, 0, 0) - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveTime) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(to_string(&NaiveTime::from_hms(0, 0, 0)).ok(), Some(r#""00:00:00""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 0, 950)).ok(), - Some(r#""00:00:00.950""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 59, 1_000)).ok(), - Some(r#""00:00:60""#.into()) - ); - assert_eq!(to_string(&NaiveTime::from_hms(0, 1, 2)).ok(), Some(r#""00:01:02""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_nano(3, 5, 7, 98765432)).ok(), - Some(r#""03:05:07.098765432""#.into()) - ); - assert_eq!(to_string(&NaiveTime::from_hms(7, 8, 9)).ok(), Some(r#""07:08:09""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_micro(12, 34, 56, 789)).ok(), - Some(r#""12:34:56.000789""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)).ok(), - Some(r#""23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""00:00:00.950""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""0:0:0.95""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""00:00:60""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 59, 1_000))); - assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms(0, 1, 2))); - assert_eq!( - from_str(r#""03:05:07.098765432""#).ok(), - Some(NaiveTime::from_hms_nano(3, 5, 7, 98765432)) - ); - assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms(7, 8, 9))); - assert_eq!( - from_str(r#""12:34:56.000789""#).ok(), - Some(NaiveTime::from_hms_micro(12, 34, 56, 789)) - ); - assert_eq!( - from_str(r#""23:59:60.999999999""#).ok(), - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) - ); - assert_eq!( - from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""000000""#).is_err()); - assert!(from_str(r#""00:00:61""#).is_err()); - assert!(from_str(r#""00:60:00""#).is_err()); - assert!(from_str(r#""24:00:00""#).is_err()); - assert!(from_str(r#""23:59:59,1""#).is_err()); - assert!(from_str(r#""012:34:56""#).is_err()); - assert!(from_str(r#""hh:mm:ss""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"86399"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"secs":0,"frac":0}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid time")) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } -} - -#[cfg(feature = "serde")] -mod serde { - use super::NaiveTime; - use core::fmt; - use serdelib::{de, ser}; - - // TODO not very optimized for space (binary formats would want something better) - // TODO round-trip for general leap seconds (not just those with second = 60) - - impl ser::Serialize for NaiveTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct NaiveTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveTimeVisitor { - type Value = NaiveTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveTimeVisitor) - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - let encoded = serialize(&t, Infinite).unwrap(); - let decoded: NaiveTime = deserialize(&encoded).unwrap(); - assert_eq!(t, decoded); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveTime; - use oldtime::Duration; - use std::u32; - use Timelike; - - #[test] - fn test_time_from_hms_milli() { - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_000_000)) - ); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_from_hms_micro() { - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 333), - Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_999_000)) - ); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_hms() { - assert_eq!(NaiveTime::from_hms(3, 5, 7).hour(), 3); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(0), Some(NaiveTime::from_hms(0, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(23), Some(NaiveTime::from_hms(23, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(24), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).minute(), 5); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(0), Some(NaiveTime::from_hms(3, 0, 7))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_minute(59), - Some(NaiveTime::from_hms(3, 59, 7)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).second(), 7); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(0), Some(NaiveTime::from_hms(3, 5, 0))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_second(59), - Some(NaiveTime::from_hms(3, 5, 59)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(u32::MAX), None); - } - - #[test] - fn test_time_add() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $sum:expr) => {{ - assert_eq!($lhs + $rhs, $sum); - //assert_eq!($rhs + $lhs, $sum); - }}; - } - - let hmsm = NaiveTime::from_hms_milli; - - check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300)); - - // regression tests for #37 - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); - } - - #[test] - fn test_time_overflowing_add() { - let hmsm = NaiveTime::from_hms_milli; - - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), - (hmsm(14, 4, 5, 678), 0) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)), - (hmsm(2, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)), - (hmsm(20, 4, 5, 678), -86_400) - ); - - // overflowing_add_signed with leap seconds may be counter-intuitive - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)), - (hmsm(3, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)), - (hmsm(3, 4, 6, 678), -86_400) - ); - } - - #[test] - fn test_time_addassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time += Duration::hours(10); - assert_eq!(time, hms(22, 12, 12)); - time += Duration::hours(10); - assert_eq!(time, hms(8, 12, 12)); - } - - #[test] - fn test_time_subassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time -= Duration::hours(10); - assert_eq!(time, hms(2, 12, 12)); - time -= Duration::hours(10); - assert_eq!(time, hms(16, 12, 12)); - } - - #[test] - fn test_time_sub() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $diff:expr) => {{ - // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` - assert_eq!($lhs.signed_duration_since($rhs), $diff); - assert_eq!($rhs.signed_duration_since($lhs), -$diff); - }}; - } - - let hmsm = NaiveTime::from_hms_milli; - - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1)); - check!( - hmsm(3, 5, 7, 200), - hmsm(2, 4, 6, 300), - Duration::seconds(3600 + 60) + Duration::milliseconds(900) - ); - - // treats the leap second as if it coincides with the prior non-leap second, - // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400)); - - // additional equality: `time1 + duration = time2` is equivalent to - // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - } - - #[test] - fn test_time_fmt() { - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 999)), "23:59:59.999"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_000)), "23:59:60"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_001)), "23:59:60.001"); - assert_eq!(format!("{}", NaiveTime::from_hms_micro(0, 0, 0, 43210)), "00:00:00.043210"); - assert_eq!(format!("{}", NaiveTime::from_hms_nano(0, 0, 0, 6543210)), "00:00:00.006543210"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "0:0:0", - "0:0:0.0000000", - "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", - "23:59:60.373929310237", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); - } - - #[test] - fn test_time_parse_from_str() { - let hms = NaiveTime::from_hms; - assert_eq!( - NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(hms(12, 34, 56)) - ); // ignore date and offset - assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); - assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); - } - - #[test] - fn test_time_format() { - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); - assert_eq!(t.format("%M").to_string(), "05"); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); - assert_eq!(t.format("%R").to_string(), "03:05"); - assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); - assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); - assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - let t = NaiveTime::from_hms_micro(3, 5, 7, 432100); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); - - let t = NaiveTime::from_hms_milli(3, 5, 7, 210); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); - - let t = NaiveTime::from_hms(3, 5, 7); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); - - // corner cases - assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM"); - assert_eq!( - NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), - "23:59:60" - ); - } -} diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs new file mode 100644 index 0000000000..5a35aa3844 --- /dev/null +++ b/src/naive/time/mod.rs @@ -0,0 +1,1668 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 time without timezone. + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{ + Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, + parse_and_remainder, write_hundreds, +}; +use crate::{FixedOffset, TimeDelta, Timelike}; +use crate::{expect, try_opt}; + +#[cfg(feature = "serde")] +mod serde; + +#[cfg(test)] +mod tests; + +/// ISO 8601 time without timezone. +/// Allows for the nanosecond precision and optional leap second representation. +/// +/// # Leap Second Handling +/// +/// Since 1960s, the manmade atomic clock has been so accurate that +/// it is much more accurate than Earth's own motion. +/// It became desirable to define the civil time in terms of the atomic clock, +/// but that risks the desynchronization of the civil time from Earth. +/// To account for this, the designers of the Coordinated Universal Time (UTC) +/// made that the UTC should be kept within 0.9 seconds of the observed Earth-bound time. +/// When the mean solar day is longer than the ideal (86,400 seconds), +/// the error slowly accumulates and it is necessary to add a **leap second** +/// to slow the UTC down a bit. +/// (We may also remove a second to speed the UTC up a bit, but it never happened.) +/// The leap second, if any, follows 23:59:59 of June 30 or December 31 in the UTC. +/// +/// Fast forward to the 21st century, +/// we have seen 26 leap seconds from January 1972 to December 2015. +/// Yes, 26 seconds. Probably you can read this paragraph within 26 seconds. +/// But those 26 seconds, and possibly more in the future, are never predictable, +/// and whether to add a leap second or not is known only before 6 months. +/// Internet-based clocks (via NTP) do account for known leap seconds, +/// but the system API normally doesn't (and often can't, with no network connection) +/// and there is no reliable way to retrieve leap second information. +/// +/// Chrono does not try to accurately implement leap seconds; it is impossible. +/// Rather, **it allows for leap seconds but behaves as if there are *no other* leap seconds.** +/// Various operations will ignore any possible leap second(s) +/// except when any of the operands were actually leap seconds. +/// +/// If you cannot tolerate this behavior, +/// you must use a separate `TimeZone` for the International Atomic Time (TAI). +/// TAI is like UTC but has no leap seconds, and thus slightly differs from UTC. +/// Chrono does not yet provide such implementation, but it is planned. +/// +/// ## Representing Leap Seconds +/// +/// The leap second is indicated via fractional seconds more than 1 second. +/// This makes possible to treat a leap second as the prior non-leap second +/// if you don't care about sub-second accuracy. +/// You should use the proper formatting to get the raw leap second. +/// +/// All methods accepting fractional seconds will accept such values. +/// +/// ``` +/// use chrono::{NaiveDate, NaiveTime}; +/// +/// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); +/// +/// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1) +/// .unwrap() +/// .and_hms_micro_opt(8, 59, 59, 1_000_000) +/// .unwrap(); +/// +/// let dt2 = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_nano_opt(23, 59, 59, 1_000_000_000) +/// .unwrap() +/// .and_utc(); +/// # let _ = (t, dt1, dt2); +/// ``` +/// +/// Note that the leap second can happen anytime given an appropriate time zone; +/// 2015-07-01 01:23:60 would be a proper leap second if UTC+01:24 had existed. +/// Practically speaking, though, by the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment. +/// +/// ## Date And Time Arithmetic +/// +/// As a concrete example, let's assume that `03:00:60` and `04:00:60` are leap seconds. +/// In reality, of course, leap seconds are separated by at least 6 months. +/// We will also use some intuitive concise notations for the explanation. +/// +/// `Time + TimeDelta` +/// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): +/// +/// - `03:00:00 + 1s = 03:00:01`. +/// - `03:00:59 + 60s = 03:01:59`. +/// - `03:00:59 + 61s = 03:02:00`. +/// - `03:00:59 + 1s = 03:01:00`. +/// - `03:00:60 + 1s = 03:01:00`. +/// Note that the sum is identical to the previous. +/// - `03:00:60 + 60s = 03:01:59`. +/// - `03:00:60 + 61s = 03:02:00`. +/// - `03:00:60.1 + 0.8s = 03:00:60.9`. +/// +/// `Time - TimeDelta` +/// (short for [`NaiveTime::overflowing_sub_signed`](#method.overflowing_sub_signed)): +/// +/// - `03:00:00 - 1s = 02:59:59`. +/// - `03:01:00 - 1s = 03:00:59`. +/// - `03:01:00 - 60s = 03:00:00`. +/// - `03:00:60 - 60s = 03:00:00`. +/// Note that the result is identical to the previous. +/// - `03:00:60.7 - 0.4s = 03:00:60.3`. +/// - `03:00:60.7 - 0.9s = 03:00:59.8`. +/// +/// `Time - Time` +/// (short for [`NaiveTime::signed_duration_since`](#method.signed_duration_since)): +/// +/// - `04:00:00 - 03:00:00 = 3600s`. +/// - `03:01:00 - 03:00:00 = 60s`. +/// - `03:00:60 - 03:00:00 = 60s`. +/// Note that the difference is identical to the previous. +/// - `03:00:60.6 - 03:00:59.4 = 1.2s`. +/// - `03:01:00 - 03:00:59.8 = 0.2s`. +/// - `03:01:00 - 03:00:60.5 = 0.5s`. +/// Note that the difference is larger than the previous, +/// even though the leap second clearly follows the previous whole second. +/// - `04:00:60.9 - 03:00:60.1 = +/// (04:00:60.9 - 04:00:00) + (04:00:00 - 03:01:00) + (03:01:00 - 03:00:60.1) = +/// 60.9s + 3540s + 0.9s = 3601.8s`. +/// +/// In general, +/// +/// - `Time + TimeDelta` unconditionally equals to `TimeDelta + Time`. +/// +/// - `Time - TimeDelta` unconditionally equals to `Time + (-TimeDelta)`. +/// +/// - `Time1 - Time2` unconditionally equals to `-(Time2 - Time1)`. +/// +/// - Associativity does not generally hold, because +/// `(Time + TimeDelta1) - TimeDelta2` no longer equals to `Time + (TimeDelta1 - TimeDelta2)` +/// for two positive durations. +/// +/// - As a special case, `(Time + TimeDelta) - TimeDelta` also does not equal to `Time`. +/// +/// - If you can assume that all durations have the same sign, however, +/// then the associativity holds: +/// `(Time + TimeDelta1) + TimeDelta2` equals to `Time + (TimeDelta1 + TimeDelta2)` +/// for two positive durations. +/// +/// ## Reading And Writing Leap Seconds +/// +/// The "typical" leap seconds on the minute boundary are +/// correctly handled both in the formatting and parsing. +/// The leap second in the human-readable representation +/// will be represented as the second part being 60, as required by ISO 8601. +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_milli_opt(23, 59, 59, 1_000) +/// .unwrap() +/// .and_utc(); +/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); +/// ``` +/// +/// There are hypothetical leap seconds not on the minute boundary nevertheless supported by Chrono. +/// They are allowed for the sake of completeness and consistency; there were several "exotic" time +/// zone offsets with fractional minutes prior to UTC after all. +/// For such cases the human-readable representation is ambiguous and would be read back to the next +/// non-leap second. +/// +/// A `NaiveTime` with a leap second that is not on a minute boundary can only be created from a +/// [`DateTime`](crate::DateTime) with fractional minutes as offset, or using +/// [`Timelike::with_nanosecond()`]. +/// +/// ``` +/// use chrono::{FixedOffset, NaiveDate, TimeZone}; +/// +/// let paramaribo_pre1945 = FixedOffset::east_opt(-13236).unwrap(); // -03:40:36 +/// let leap_sec_2015 = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); +/// let dt1 = paramaribo_pre1945.from_utc_datetime(&leap_sec_2015); +/// assert_eq!(format!("{:?}", dt1), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt1.time()), "20:19:24"); +/// +/// let next_sec = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); +/// let dt2 = paramaribo_pre1945.from_utc_datetime(&next_sec); +/// assert_eq!(format!("{:?}", dt2), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt2.time()), "20:19:24"); +/// +/// assert!(dt1.time() != dt2.time()); +/// assert!(dt1.time().to_string() == dt2.time().to_string()); +/// ``` +/// +/// Since Chrono alone cannot determine any existence of leap seconds, +/// **there is absolutely no guarantee that the leap second read has actually happened**. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct NaiveTime { + secs: u32, + frac: u32, +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for NaiveTime { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mins = u.int_in_range(0..=1439)?; + let mut secs = u.int_in_range(0..=60)?; + let mut nano = u.int_in_range(0..=999_999_999)?; + if secs == 60 { + secs = 59; + nano += 1_000_000_000; + } + let time = NaiveTime::from_num_seconds_from_midnight_opt(mins * 60 + secs, nano) + .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); + Ok(time) + } +} + +impl NaiveTime { + /// Makes a new `NaiveTime` from hour, minute and second. + /// + /// No [leap second](#leap-second-handling) is allowed here; + /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. + /// + /// # Panics + /// + /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { + expect(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute and second. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute and/or second. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hms_opt = NaiveTime::from_hms_opt; + /// + /// assert!(from_hms_opt(0, 0, 0).is_some()); + /// assert!(from_hms_opt(23, 59, 59).is_some()); + /// assert!(from_hms_opt(24, 0, 0).is_none()); + /// assert!(from_hms_opt(23, 60, 0).is_none()); + /// assert!(from_hms_opt(23, 59, 60).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { + NaiveTime::from_hms_nano_opt(hour, min, sec, 0) + } + + /// Makes a new `NaiveTime` from hour, minute, second and millisecond. + /// + /// The millisecond part can exceed 1,000 + /// in order to represent the [leap second](#leap-second-handling). + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { + expect(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsm_opt = NaiveTime::from_hms_milli_opt; + /// + /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); + /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_milli_opt( + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Option { + let nano = try_opt!(milli.checked_mul(1_000_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) + } + + /// Makes a new `NaiveTime` from hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or microsecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { + expect(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsu_opt = NaiveTime::from_hms_micro_opt; + /// + /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); + /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_micro_opt( + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Option { + let nano = try_opt!(micro.checked_mul(1_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) + } + + /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { + expect(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsn_opt = NaiveTime::from_hms_nano_opt; + /// + /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); + /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { + if (hour >= 24 || min >= 60 || sec >= 60) + || (nano >= 1_000_000_000 && sec != 59) + || nano >= 2_000_000_000 + { + return None; + } + let secs = hour * 3600 + min * 60 + sec; + Some(NaiveTime { secs, frac: nano }) + } + + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. + /// + /// # Panics + /// + /// Panics on invalid number of seconds and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { + expect(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") + } + + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid number of seconds and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight_opt; + /// + /// assert!(from_nsecs_opt(0, 0).is_some()); + /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); + /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_nsecs_opt(86_400, 0).is_none()); + /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { + if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { + return None; + } + Some(NaiveTime { secs, frac: nano }) + } + + /// Parses a string with the specified format string and returns a new `NaiveTime`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let parse_from_str = NaiveTime::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("23:56:04", "%H:%M:%S"), + /// Ok(NaiveTime::from_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("pm012345.6789", "%p%I%M%S%.f"), + /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap()) + /// ); + /// ``` + /// + /// Date and offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + /// + /// [Leap seconds](#leap-second-handling) are correctly handled by + /// treating any time of the form `hh:mm:60` as a leap second. + /// (This equally applies to the formatting, so the round trip is possible.) + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("08:59:60.123", "%H:%M:%S%.f"), + /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap()) + /// ); + /// ``` + /// + /// Missing seconds are assumed to be zero, + /// but out-of-bound times or insufficient fields are errors otherwise. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!(parse_from_str("7:15", "%H:%M"), Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); + /// + /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); + /// assert!(parse_from_str("12", "%H").is_err()); + /// assert!(parse_from_str("17:60", "%H:%M").is_err()); + /// assert!(parse_from_str("24:00:00", "%H:%M:%S").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// Here `%H` is for 24-hour clocks, unlike `%I`, + /// and thus can be independently determined without AM/PM. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert!(parse_from_str("13:07 AM", "%H:%M %p").is_err()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time() + } + + /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveTime}; + /// let (time, remainder) = + /// NaiveTime::parse_and_remainder("3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); + /// assert_eq!(time, NaiveTime::from_hms_opt(3, 4, 33).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time().map(|t| (t, remainder)) + } + + /// Adds given `TimeDelta` to the current time, and also returns the number of *seconds* + /// in the integral number of days ignored from the addition. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), + /// (from_hms(14, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), + /// (from_hms(2, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), + /// (from_hms(20, 4, 5), -86_400) + /// ); + /// ``` + #[must_use] + pub const fn overflowing_add_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { + let mut secs = self.secs as i64; + let mut frac = self.frac as i32; + let secs_to_add = rhs.num_seconds(); + let frac_to_add = rhs.subsec_nanos(); + + // Check if `self` is a leap second and adding `rhs` would escape that leap second. + // If that is the case, update `frac` and `secs` to involve no leap second. + // If it stays within the leap second or the second before, and only adds a fractional + // second, just do that and return (this way the rest of the code can ignore leap seconds). + if frac >= 1_000_000_000 { + // check below is adjusted to not overflow an i32: `frac + frac_to_add >= 2_000_000_000` + if secs_to_add > 0 || (frac_to_add > 0 && frac >= 2_000_000_000 - frac_to_add) { + frac -= 1_000_000_000; + } else if secs_to_add < 0 { + frac -= 1_000_000_000; + secs += 1; + } else { + return (NaiveTime { secs: self.secs, frac: (frac + frac_to_add) as u32 }, 0); + } + } + + let mut secs = secs + secs_to_add; + frac += frac_to_add; + + if frac < 0 { + frac += 1_000_000_000; + secs -= 1; + } else if frac >= 1_000_000_000 { + frac -= 1_000_000_000; + secs += 1; + } + + let secs_in_day = secs.rem_euclid(86_400); + let remaining = secs - secs_in_day; + (NaiveTime { secs: secs_in_day as u32, frac: frac as u32 }, remaining) + } + + /// Subtracts given `TimeDelta` from the current time, and also returns the number of *seconds* + /// in the integral number of days ignored from the subtraction. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(2).unwrap()), + /// (from_hms(1, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(17).unwrap()), + /// (from_hms(10, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(-22).unwrap()), + /// (from_hms(1, 4, 5), -86_400) + /// ); + /// ``` + #[inline] + #[must_use] + pub const fn overflowing_sub_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { + let (time, rhs) = self.overflowing_add_signed(rhs.neg()); + (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) + } + + /// Subtracts another `NaiveTime` from the current time. + /// Returns a `TimeDelta` within +/- 1 day. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); + /// let since = NaiveTime::signed_duration_since; + /// + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), + /// TimeDelta::try_milliseconds(25).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), + /// TimeDelta::try_milliseconds(975).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), + /// TimeDelta::try_seconds(7).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), + /// TimeDelta::try_seconds(5 * 60).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), + /// TimeDelta::try_seconds(3 * 3600).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), + /// TimeDelta::try_seconds(-3600).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() + /// ); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveTime}; + /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; + /// # let since = NaiveTime::signed_duration_since; + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), + /// TimeDelta::try_seconds(1).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), + /// TimeDelta::try_milliseconds(1500).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), + /// TimeDelta::try_seconds(60).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), + /// TimeDelta::try_seconds(1).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), + /// TimeDelta::try_seconds(61).unwrap()); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: NaiveTime) -> TimeDelta { + // | | :leap| | | | | | | :leap| | + // | | : | | | | | | | : | | + // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- + // | `rhs` | | `self` + // |======================================>| | + // | | `self.secs - rhs.secs` |`self.frac` + // |====>| | |======>| + // `rhs.frac`|========================================>| + // | | | `self - rhs` | | + + let mut secs = self.secs as i64 - rhs.secs as i64; + let frac = self.frac as i64 - rhs.frac as i64; + + // `secs` may contain a leap second yet to be counted + if self.secs > rhs.secs && rhs.frac >= 1_000_000_000 { + secs += 1; + } else if self.secs < rhs.secs && self.frac >= 1_000_000_000 { + secs -= 1; + } + + let secs_from_frac = frac.div_euclid(1_000_000_000); + let frac = frac.rem_euclid(1_000_000_000) as u32; + + expect(TimeDelta::new(secs + secs_from_frac, frac), "must be in range") + } + + /// Adds given `FixedOffset` to the current time, and returns the number of days that should be + /// added to a date as a result of the offset (either `-1`, `0`, or `1` because the offset is + /// always less than 24h). + /// + /// This method is similar to [`overflowing_add_signed`](#method.overflowing_add_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_add_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 + offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + + /// Subtracts given `FixedOffset` from the current time, and returns the number of days that + /// should be added to a date as a result of the offset (either `-1`, `0`, or `1` because the + /// offset is always less than 24h). + /// + /// This method is similar to [`overflowing_sub_signed`](#method.overflowing_sub_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_sub_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 - offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + + /// Formats the time with the specified formatting items. + /// Otherwise it is the same as the ordinary [`format`](#method.format) method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveTime; + /// + /// let fmt = StrftimeItems::new("%H:%M:%S"); + /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); + /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(None, Some(*self), items) + } + + /// Formats the time with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// A wrong format string does *not* issue an error immediately. + /// Rather, converting or formatting the `DelayedFormat` fails. + /// You are recommended to immediately use `DelayedFormat` for this reason. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); + /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); + /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); + /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Returns a triple of the hour, minute and second numbers. + pub(crate) fn hms(&self) -> (u32, u32, u32) { + let sec = self.secs % 60; + let mins = self.secs / 60; + let min = mins % 60; + let hour = mins / 60; + (hour, min, sec) + } + + /// Returns the number of non-leap seconds past the last midnight. + // This duplicates `Timelike::num_seconds_from_midnight()`, because trait methods can't be const + // yet. + #[inline] + pub(crate) const fn num_seconds_from_midnight(&self) -> u32 { + self.secs + } + + /// Returns the number of nanoseconds since the whole non-leap second. + // This duplicates `Timelike::nanosecond()`, because trait methods can't be const yet. + #[inline] + pub(crate) const fn nanosecond(&self) -> u32 { + self.frac + } + + /// The earliest possible `NaiveTime` + pub const MIN: Self = Self { secs: 0, frac: 0 }; + pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; +} + +impl Timelike for NaiveTime { + /// Returns the hour number from 0 to 23. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().hour(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().hour(), 23); + /// ``` + #[inline] + fn hour(&self) -> u32 { + self.hms().0 + } + + /// Returns the minute number from 0 to 59. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().minute(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().minute(), 56); + /// ``` + #[inline] + fn minute(&self) -> u32 { + self.hms().1 + } + + /// Returns the second number from 0 to 59. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().second(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().second(), 4); + /// ``` + /// + /// This method never returns 60 even when it is a leap second. + /// ([Why?](#leap-second-handling)) + /// Use the proper [formatting method](#method.format) to get a human-readable representation. + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// # use chrono::{NaiveTime, Timelike}; + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); + /// assert_eq!(leap.second(), 59); + /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); + /// # } + /// ``` + #[inline] + fn second(&self) -> u32 { + self.hms().2 + } + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](#leap-second-handling). + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().nanosecond(), 0); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().nanosecond(), + /// 12_345_678 + /// ); + /// ``` + /// + /// Leap seconds may have seemingly out-of-range return values. + /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or + /// use the proper [formatting method](#method.format) to get a human-readable representation. + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// # use chrono::{NaiveTime, Timelike}; + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); + /// assert_eq!(leap.nanosecond(), 1_000_000_000); + /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); + /// # } + /// ``` + #[inline] + fn nanosecond(&self) -> u32 { + self.frac + } + + /// Makes a new `NaiveTime` with the hour number changed. + /// + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano_opt(7, 56, 4, 12_345_678).unwrap())); + /// assert_eq!(dt.with_hour(24), None); + /// ``` + #[inline] + fn with_hour(&self, hour: u32) -> Option { + if hour >= 24 { + return None; + } + let secs = hour * 3600 + self.secs % 3600; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with the minute number changed. + /// + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_minute(45), + /// Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap()) + /// ); + /// assert_eq!(dt.with_minute(60), None); + /// ``` + #[inline] + fn with_minute(&self, min: u32) -> Option { + if min >= 60 { + return None; + } + let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_second(17), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap()) + /// ); + /// assert_eq!(dt.with_second(60), None); + /// ``` + #[inline] + fn with_second(&self, sec: u32) -> Option { + if sec >= 60 { + return None; + } + let secs = self.secs / 60 * 60 + sec; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. + /// + /// As with the [`nanosecond`](#method.nanosecond) method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap()) + /// ); + /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); + /// ``` + /// + /// Leap seconds can theoretically follow *any* whole second. + /// The following would be a proper leap second at the time zone offset of UTC-00:03:57 + /// (there are several historical examples comparable to this "non-sense" offset), + /// and therefore is allowed. + /// + /// ``` + /// # use chrono::{NaiveTime, Timelike}; + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// let strange_leap_second = dt.with_nanosecond(1_333_333_333).unwrap(); + /// assert_eq!(strange_leap_second.nanosecond(), 1_333_333_333); + /// ``` + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option { + if nano >= 2_000_000_000 { + return None; + } + Some(NaiveTime { frac: nano, ..*self }) + } + + /// Returns the number of non-leap seconds past the last midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), 3723); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), + /// 86164 + /// ); + /// assert_eq!( + /// NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), + /// 86399 + /// ); + /// ``` + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { + self.secs // do not repeat the calculation! + } +} + +/// Add `TimeDelta` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(60 + 4).unwrap(), +/// from_hmsm(3, 6, 11, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(7 * 60 * 60 - 6 * 60).unwrap(), +/// from_hmsm(9, 59, 7, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 7, 80) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 8, 230) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(-980).unwrap(), +/// from_hmsm(3, 5, 6, 970) +/// ); +/// ``` +/// +/// The addition wraps around. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(22*60*60).unwrap(), from_hmsm(1, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), from_hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), from_hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), from_hmsm(3, 5, 59, 300)); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: TimeDelta) -> NaiveTime { + self.overflowing_add_signed(rhs).0 + } +} + +/// Add-assign `TimeDelta` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveTime { + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `TimeDelta`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); + self.overflowing_add_signed(d).0 + } +} + +/// Add-assign `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +/// Add `FixedOffset` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_add_offset(rhs).0 + } +} + +/// Subtract `TimeDelta` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +/// This is the same as addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(60 + 5).unwrap(), +/// from_hmsm(3, 4, 2, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(2 * 60 * 60 + 6 * 60).unwrap(), +/// from_hmsm(0, 59, 7, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 6, 920) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) - TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 7, 670) +/// ); +/// ``` +/// +/// The subtraction wraps around. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), from_hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), from_hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), from_hmsm(3, 6, 0, 300)); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: TimeDelta) -> NaiveTime { + self.overflowing_sub_signed(rhs).0 + } +} + +/// Subtract-assign `TimeDelta` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtract `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveTime { + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `TimeDelta`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); + self.overflowing_sub_signed(d).0 + } +} + +/// Subtract-assign `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +/// Subtract `FixedOffset` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_sub_offset(rhs).0 + } +} + +/// Subtracts another `NaiveTime` from the current time. +/// Returns a `TimeDelta` within +/- 1 day. +/// This does not overflow or underflow at all. +/// +/// As a part of Chrono's [leap second handling](#leap-second-handling), +/// the subtraction assumes that **there is no leap second ever**, +/// except when any of the `NaiveTime`s themselves represents a leap second +/// in which case the assumption becomes that +/// **there are exactly one (or two) leap second(s) ever**. +/// +/// The implementation is a wrapper around +/// [`NaiveTime::signed_duration_since`](#method.signed_duration_since). +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), +/// TimeDelta::try_milliseconds(25).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), +/// TimeDelta::try_milliseconds(975).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), +/// TimeDelta::try_seconds(7).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), +/// TimeDelta::try_seconds(5 * 60).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), +/// TimeDelta::try_seconds(3 * 3600).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), +/// TimeDelta::try_seconds(-3600).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() +/// ); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that +/// there were no other leap seconds happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::try_seconds(1).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), +/// TimeDelta::try_milliseconds(1500).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::try_seconds(60).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::try_seconds(1).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), +/// TimeDelta::try_seconds(61).unwrap()); +/// ``` +impl Sub for NaiveTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// The `Debug` output of the naive time `t` is the same as +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveTime; +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); +/// ``` +impl fmt::Debug for NaiveTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (hour, min, sec) = self.hms(); + let (sec, nano) = if self.frac >= 1_000_000_000 { + (sec + 1, self.frac - 1_000_000_000) + } else { + (sec, self.frac) + }; + + use core::fmt::Write; + write_hundreds(f, hour as u8)?; + f.write_char(':')?; + write_hundreds(f, min as u8)?; + f.write_char(':')?; + write_hundreds(f, sec as u8)?; + + if nano == 0 { + Ok(()) + } else if nano % 1_000_000 == 0 { + write!(f, ".{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(f, ".{:06}", nano / 1_000) + } else { + write!(f, ".{nano:09}") + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveTime { + fn format(&self, fmt: defmt::Formatter) { + let (hour, min, sec) = self.hms(); + let (sec, nano) = if self.frac >= 1_000_000_000 { + (sec + 1, self.frac - 1_000_000_000) + } else { + (sec, self.frac) + }; + + let (hour, min, sec) = (hour as u8, min as u8, sec as u8); + defmt::write!(fmt, "{:02}:{:02}:{:02}", hour, min, sec); + + if nano == 0 { + return; + } else if nano % 1_000_000 == 0 { + defmt::write!(fmt, ".{:03}", nano / 1_000_000); + } else if nano % 1_000 == 0 { + defmt::write!(fmt, ".{:06}", nano / 1_000); + } else { + defmt::write!(fmt, ".{:09}", nano); + } + } +} + +/// The `Display` output of the naive time `t` is the same as +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// assert_eq!(format!("{}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveTime; +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); +/// ``` +impl fmt::Display for NaiveTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Parsing a `str` into a `NaiveTime` uses the same format, +/// [`%H:%M:%S%.f`](crate::format::strftime), as in `Debug` and `Display`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); +/// assert_eq!("23:56:04".parse::(), Ok(t)); +/// +/// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); +/// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); +/// +/// let t = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_234_567_890).unwrap(); // leap second +/// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); +/// +/// // Seconds are optional +/// let t = NaiveTime::from_hms_opt(23, 56, 0).unwrap(); +/// assert_eq!("23:56".parse::(), Ok(t)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const HOUR_AND_MINUTE: &[Item<'static>] = &[ + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + ]; + const SECOND_AND_NANOS: &[Item<'static>] = &[ + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; + const TRAILING_WHITESPACE: [Item<'static>; 1] = [Item::Space("")]; + + let mut parsed = Parsed::new(); + let s = parse_and_remainder(&mut parsed, s, HOUR_AND_MINUTE.iter())?; + // Seconds are optional, don't fail if parsing them doesn't succeed. + let s = parse_and_remainder(&mut parsed, s, SECOND_AND_NANOS.iter()).unwrap_or(s); + parse(&mut parsed, s, TRAILING_WHITESPACE.iter())?; + parsed.to_naive_time() + } +} + +/// The default value for a NaiveTime is midnight, 00:00:00 exactly. +/// +/// # Example +/// +/// ```rust +/// use chrono::NaiveTime; +/// +/// let default_time = NaiveTime::default(); +/// assert_eq!(default_time, NaiveTime::from_hms_opt(0, 0, 0).unwrap()); +/// ``` +impl Default for NaiveTime { + fn default() -> Self { + NaiveTime::from_hms_opt(0, 0, 0).unwrap() + } +} diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs new file mode 100644 index 0000000000..f9b1dde215 --- /dev/null +++ b/src/naive/time/serde.rs @@ -0,0 +1,143 @@ +use super::NaiveTime; +use core::fmt; +use serde::{de, ser}; + +// TODO not very optimized for space (binary formats would want something better) +// TODO round-trip for general leap seconds (not just those with second = 60) + +impl ser::Serialize for NaiveTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } +} + +struct NaiveTimeVisitor; + +impl de::Visitor<'_> for NaiveTimeVisitor { + type Value = NaiveTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveTimeVisitor) + } +} + +#[cfg(test)] +mod tests { + use crate::NaiveTime; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(), + Some(r#""00:00:00.950""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(), + Some(r#""00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(), + Some(r#""00:01:02""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(), + Some(r#""03:05:07.098765432""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(), + Some(r#""07:08:09""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(), + Some(r#""12:34:56.000789""#.into()) + ); + let leap = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap(); + assert_eq!(serde_json::to_string(&leap).ok(), Some(r#""23:59:60.999999999""#.into())); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!( + from_str(r#""00:00:00.950""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""0:0:0.95""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""00:00:60""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()) + ); + assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap())); + assert_eq!( + from_str(r#""03:05:07.098765432""#).ok(), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()) + ); + assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap())); + assert_eq!( + from_str(r#""12:34:56.000789""#).ok(), + Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.999999999""#).ok(), + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""000000""#).is_err()); + assert!(from_str(r#""00:00:61""#).is_err()); + assert!(from_str(r#""00:60:00""#).is_err()); + assert!(from_str(r#""24:00:00""#).is_err()); + assert!(from_str(r#""23:59:59,1""#).is_err()); + assert!(from_str(r#""012:34:56""#).is_err()); + assert!(from_str(r#""hh:mm:ss""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"86399"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + let encoded = serialize(&t).unwrap(); + let decoded: NaiveTime = deserialize(&encoded).unwrap(); + assert_eq!(t, decoded); + } +} diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs new file mode 100644 index 0000000000..d1df20cf1e --- /dev/null +++ b/src/naive/time/tests.rs @@ -0,0 +1,390 @@ +use super::NaiveTime; +use crate::{FixedOffset, TimeDelta, Timelike}; + +#[test] +fn test_time_from_hms_milli() { + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 777), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None); +} + +#[test] +fn test_time_from_hms_micro() { + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 333), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 333_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None); +} + +#[test] +fn test_time_hms() { + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().hour(), 3); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(0), + Some(NaiveTime::from_hms_opt(0, 5, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(23), + Some(NaiveTime::from_hms_opt(23, 5, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(24), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().minute(), 5); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(0), + Some(NaiveTime::from_hms_opt(3, 0, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(59), + Some(NaiveTime::from_hms_opt(3, 59, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().second(), 7); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(0), + Some(NaiveTime::from_hms_opt(3, 5, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(59), + Some(NaiveTime::from_hms_opt(3, 5, 59).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(u32::MAX), None); +} + +#[test] +fn test_time_add() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $sum:expr) => {{ + assert_eq!($lhs + $rhs, $sum); + //assert_eq!($rhs + $lhs, $sum); + }}; + } + + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-1800).unwrap(), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-800).unwrap(), hmsm(3, 5, 59, 500)); + check!( + hmsm(3, 5, 59, 1_300), + TimeDelta::try_milliseconds(-100).unwrap(), + hmsm(3, 5, 59, 1_200) + ); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(1800).unwrap(), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300)); + + // regression tests for #37 + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-990).unwrap(), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-9990).unwrap(), hmsm(23, 59, 50, 10)); +} + +#[test] +fn test_time_overflowing_add() { + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), + (hmsm(14, 4, 5, 678), 0) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), + (hmsm(2, 4, 5, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), + (hmsm(20, 4, 5, 678), -86_400) + ); + + // overflowing_add_signed with leap seconds may be counter-intuitive + assert_eq!( + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(1).unwrap()), + (hmsm(3, 4, 59, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(-1).unwrap()), + (hmsm(3, 5, 0, 678), -86_400) + ); +} + +#[test] +fn test_time_addassignment() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + let mut time = hms(12, 12, 12); + time += TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(22, 12, 12)); + time += TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(8, 12, 12)); +} + +#[test] +fn test_time_subassignment() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + let mut time = hms(12, 12, 12); + time -= TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(2, 12, 12)); + time -= TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(16, 12, 12)); +} + +#[test] +fn test_time_sub() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $diff:expr) => {{ + // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` + assert_eq!($lhs.signed_duration_since($rhs), $diff); + assert_eq!($rhs.signed_duration_since($lhs), -$diff); + }}; + } + + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero()); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::try_milliseconds(300).unwrap()); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap()); + check!( + hmsm(3, 5, 7, 200), + hmsm(2, 4, 6, 300), + TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::try_milliseconds(900).unwrap() + ); + + // treats the leap second as if it coincides with the prior non-leap second, + // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::try_milliseconds(400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::try_milliseconds(1400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::try_milliseconds(1400).unwrap()); + + // additional equality: `time1 + duration = time2` is equivalent to + // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. + assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut t = NaiveTime::from_hms_opt(11, 34, 23).unwrap(); + let same = t + Duration::ZERO; + assert_eq!(t, same); + + t += Duration::new(3600, 0); + assert_eq!(t, NaiveTime::from_hms_opt(12, 34, 23).unwrap()); + + t -= Duration::new(7200, 0); + assert_eq!(t, NaiveTime::from_hms_opt(10, 34, 23).unwrap()); +} + +#[test] +fn test_time_fmt() { + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 999).unwrap()), + "23:59:59.999" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap()), + "23:59:60" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_001).unwrap()), + "23:59:60.001" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_micro_opt(0, 0, 0, 43210).unwrap()), + "00:00:00.043210" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_nano_opt(0, 0, 0, 6543210).unwrap()), + "00:00:00.006543210" + ); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!( + format!("{:30}", NaiveTime::from_hms_milli_opt(3, 5, 7, 9).unwrap()), + "03:05:07.009" + ); +} + +#[test] +fn test_time_from_str() { + // valid cases + let valid = [ + "0:0:0", + "0:0:0.0000000", + "0:0:0.0000003", + " 4 : 3 : 2.1 ", + " 09:08:07 ", + " 09:08 ", + " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", + "23:59:60.373929310237", + ]; + for &s in &valid { + eprintln!("test_time_parse_from_str valid {s:?}"); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{s}` has failed: {e}"), + }; + let s_ = format!("{d:?}"); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") + } + }; + assert!( + d == d_, + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8:", // trailing colon + "15:8:x", // invalid data + "15:8:9x", // invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {s:?}"); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_time_parse_from_str() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + assert_eq!( + NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(hms(12, 34, 56)) + ); // ignore date and offset + assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); + assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); +} + +#[test] +fn test_overflowing_offset() { + let hmsm = |h, m, s, n| NaiveTime::from_hms_milli_opt(h, m, s, n).unwrap(); + + let positive_offset = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + // regular time + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(9, 6, 7, 890), 0)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(1, 6, 7, 890), 0)); + // leap second is preserved, and wrap to next day + let t = hmsm(23, 59, 59, 1_000); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(3, 59, 59, 1_000), 1)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(19, 59, 59, 1_000), 0)); + // wrap to previous day + let t = hmsm(1, 2, 3, 456); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(21, 2, 3, 456), -1)); + // an odd offset + let negative_offset = FixedOffset::west_opt(((2 * 60) + 3) * 60 + 4).unwrap(); + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(negative_offset), (hmsm(3, 3, 3, 890), 0)); + assert_eq!(t.overflowing_sub_offset(negative_offset), (hmsm(7, 9, 11, 890), 0)); + + assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset); + assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset); +} + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let t_min = NaiveTime::MIN; + let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_min); + + let t_max = NaiveTime::MAX; + let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_max); +} diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 441d0f63c1..e810c854fd 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -4,22 +4,29 @@ //! The time zone which has a fixed offset from UTC. use core::fmt; -use core::ops::{Add, Sub}; -use oldtime::Duration as OldDuration; +use core::str::FromStr; -use super::{LocalResult, Offset, TimeZone}; -use div::div_mod_floor; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use DateTime; -use Timelike; +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::{MappedLocalTime, Offset, TimeZone}; +use crate::format::{OUT_OF_RANGE, ParseError, scan}; +use crate::naive::{NaiveDate, NaiveDateTime}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on a `FixedOffset` struct is the preferred way to construct -/// `DateTime` instances. See the [`east`](#method.east) and -/// [`west`](#method.west) methods for examples. +/// `DateTime` instances. See the [`east_opt`](#method.east_opt) and +/// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct FixedOffset { local_minus_utc: i32, } @@ -29,16 +36,8 @@ impl FixedOffset { /// The negative `secs` means the Western Hemisphere. /// /// Panics on the out-of-bound `secs`. - /// - /// # Example - /// - /// ``` - /// use chrono::{FixedOffset, TimeZone}; - /// let hour = 3600; - /// let datetime = FixedOffset::east(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); - /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") - /// ``` + #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")] + #[must_use] pub fn east(secs: i32) -> FixedOffset { FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") } @@ -47,7 +46,20 @@ impl FixedOffset { /// The negative `secs` means the Western Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. - pub fn east_opt(secs: i32) -> Option { + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::{FixedOffset, TimeZone}; + /// let hour = 3600; + /// let datetime = + /// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); + /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") + /// # } + /// ``` + #[must_use] + pub const fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) } else { @@ -59,16 +71,8 @@ impl FixedOffset { /// The negative `secs` means the Eastern Hemisphere. /// /// Panics on the out-of-bound `secs`. - /// - /// # Example - /// - /// ``` - /// use chrono::{FixedOffset, TimeZone}; - /// let hour = 3600; - /// let datetime = FixedOffset::west(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); - /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") - /// ``` + #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")] + #[must_use] pub fn west(secs: i32) -> FixedOffset { FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") } @@ -77,7 +81,20 @@ impl FixedOffset { /// The negative `secs` means the Eastern Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. - pub fn west_opt(secs: i32) -> Option { + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::{FixedOffset, TimeZone}; + /// let hour = 3600; + /// let datetime = + /// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); + /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") + /// # } + /// ``` + #[must_use] + pub const fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) } else { @@ -87,17 +104,26 @@ impl FixedOffset { /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] - pub fn local_minus_utc(&self) -> i32 { + pub const fn local_minus_utc(&self) -> i32 { self.local_minus_utc } /// Returns the number of seconds to add to convert from the local time to UTC. #[inline] - pub fn utc_minus_local(&self) -> i32 { + pub const fn utc_minus_local(&self) -> i32 { -self.local_minus_utc } } +/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). +impl FromStr for FixedOffset { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; + Self::east_opt(offset).ok_or(OUT_OF_RANGE) + } +} + impl TimeZone for FixedOffset { type Offset = FixedOffset; @@ -105,11 +131,11 @@ impl TimeZone for FixedOffset { *offset } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(*self) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(*self) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { @@ -130,12 +156,14 @@ impl fmt::Debug for FixedOffset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let offset = self.local_minus_utc; let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; - let (mins, sec) = div_mod_floor(offset, 60); - let (hour, min) = div_mod_floor(mins, 60); + let sec = offset.rem_euclid(60); + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); if sec == 0 { - write!(f, "{}{:02}:{:02}", sign, hour, min) + write!(f, "{sign}{hour:02}:{min:02}") } else { - write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + write!(f, "{sign}{hour:02}:{min:02}:{sec:02}") } } } @@ -146,99 +174,80 @@ impl fmt::Display for FixedOffset { } } -// addition or subtraction of FixedOffset to/from Timelike values is the same as -// adding or subtracting the offset's local_minus_utc value -// but keep keeps the leap second information. -// this should be implemented more efficiently, but for the time being, this is generic right now. - -fn add_with_leapsecond(lhs: &T, rhs: i32) -> T -where - T: Timelike + Add, -{ - // extract and temporarily remove the fractional part and later recover it - let nanos = lhs.nanosecond(); - let lhs = lhs.with_nanosecond(0).unwrap(); - (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() -} - -impl Add for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) +#[cfg(feature = "defmt")] +impl defmt::Format for FixedOffset { + fn format(&self, f: defmt::Formatter) { + let offset = self.local_minus_utc; + let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; + let sec = offset.rem_euclid(60); + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); + if sec == 0 { + defmt::write!(f, "{}{:02}:{:02}", sign, hour, min) + } else { + defmt::write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + } } } -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for FixedOffset { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let secs = u.int_in_range(-86_399..=86_399)?; + let fixed_offset = FixedOffset::east_opt(secs) + .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); + Ok(fixed_offset) } } #[cfg(test)] mod tests { use super::FixedOffset; - use offset::TimeZone; + use crate::offset::TimeZone; + use std::str::FromStr; #[test] fn test_date_extreme_offset() { // starting from 0.3 we don't have an offset exceeding one day. // this makes everything easier! + let offset = FixedOffset::east_opt(86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)), - "2012-02-29+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07+23:59:59" ); + let offset = FixedOffset::east_opt(-86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)), - "2012-02-29T05:06:07+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)), - "2012-03-04-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(-86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)), - "2012-03-04T05:06:07-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07+23:59:59" ); } + + #[test] + fn test_parse_offset() { + let offset = FixedOffset::from_str("-0500").unwrap(); + assert_eq!(offset.local_minus_utc, -5 * 3600); + let offset = FixedOffset::from_str("-08:00").unwrap(); + assert_eq!(offset.local_minus_utc, -8 * 3600); + let offset = FixedOffset::from_str("+06:30").unwrap(); + assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let offset = FixedOffset::from_str("-0500").unwrap(); + let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), offset); + } } diff --git a/src/offset/local.rs b/src/offset/local.rs deleted file mode 100644 index deb3d681fe..0000000000 --- a/src/offset/local.rs +++ /dev/null @@ -1,227 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! The local (system) time zone. - -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use sys::{self, Timespec}; - -use super::fixed::FixedOffset; -use super::{LocalResult, TimeZone}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use naive::NaiveTime; -use naive::{NaiveDate, NaiveDateTime}; -use {Date, DateTime}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use {Datelike, Timelike}; - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn tm_to_datetime(mut tm: sys::Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - #[cfg(not(windows))] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { - // from_yo is more efficient than from_ymd (since it's the internal representation). - NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) - } - - #[cfg(windows)] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { - // ...but tm_yday is broken in Windows (issue #85) - NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) - } - - let date = tm_to_naive_date(&tm); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - let offset = FixedOffset::east(tm.tm_utcoff); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec { - // well, this exploits an undocumented `Tm::to_timespec` behavior - // to get the exact function we want (either `timegm` or `mktime`). - // the number 1 is arbitrary but should be non-zero to trigger `mktime`. - let tm_utcoff = if local { 1 } else { 0 }; - - let tm = sys::Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - tm_utcoff: tm_utcoff, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - tm.to_timespec() -} - -/// The local timescale. This is implemented via the standard `time` crate. -/// -/// Using the [`TimeZone`](./trait.TimeZone.html) methods -/// on the Local struct is the preferred way to construct `DateTime` -/// instances. -/// -/// # Example -/// -/// ``` -/// use chrono::{Local, DateTime, TimeZone}; -/// -/// let dt: DateTime = Local::now(); -/// let dt: DateTime = Local.timestamp(0, 0); -/// ``` -#[derive(Copy, Clone, Debug)] -pub struct Local; - -impl Local { - /// Returns a `Date` which corresponds to the current date. - pub fn today() -> Date { - Local::now().date() - } - - /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - pub fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) - } - - /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - pub fn now() -> DateTime { - use super::Utc; - let now: DateTime = super::Utc::now(); - - // Workaround missing timezone logic in `time` crate - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - DateTime::from_utc(now.naive_utc(), offset) - } -} - -impl TimeZone for Local { - type Offset = FixedOffset; - - fn from_offset(_offset: &FixedOffset) -> Local { - Local - } - - // they are easier to define in terms of the finished date and time unlike other offsets - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { - self.from_local_date(local).map(|date| *date.offset()) - } - - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { - self.from_local_datetime(local).map(|datetime| *datetime.offset()) - } - - fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { - *self.from_utc_date(utc).offset() - } - - fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { - *self.from_utc_datetime(utc).offset() - } - - // override them for avoiding redundant works - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - // this sounds very strange, but required for keeping `TimeZone::ymd` sane. - // in the other words, we use the offset at the local midnight - // but keep the actual date unaltered (much like `FixedOffset`). - let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0)); - midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let mut local = local.clone(); - // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - local -= ::Duration::seconds(offset.local_minus_utc() as i64); - LocalResult::Single(DateTime::from_utc(local, offset)) - } - - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let timespec = datetime_to_timespec(local, true); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = local.nanosecond() as i32; - - LocalResult::Single(tm_to_datetime(tm)) - } - - fn from_utc_date(&self, utc: &NaiveDate) -> Date { - let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0)); - Date::from_utc(*utc, *midnight.offset()) - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - DateTime::from_utc(*utc, offset) - } - - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - let timespec = datetime_to_timespec(utc, false); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = utc.nanosecond() as i32; - - tm_to_datetime(tm) - } -} - -#[cfg(test)] -mod tests { - use super::Local; - use offset::TimeZone; - use Datelike; - - #[test] - fn test_local_date_sanity_check() { - // issue #27 - assert_eq!(Local.ymd(2999, 12, 28).day(), 28); - } - - #[test] - fn test_leap_second() { - // issue #123 - let today = Local::today(); - - let dt = today.and_hms_milli(1, 2, 59, 1000); - let timestr = dt.time().to_string(); - // the OS API may or may not support the leap second, - // but there are only two sensible options. - assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr); - - let dt = today.and_hms_milli(1, 2, 3, 1234); - let timestr = dt.time().to_string(); - assert!( - timestr == "01:02:03.234" || timestr == "01:02:04.234", - "unexpected timestr {:?}", - timestr - ); - } -} diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs new file mode 100644 index 0000000000..e3e7b0f959 --- /dev/null +++ b/src/offset/local/mod.rs @@ -0,0 +1,543 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! The local (system) time zone. + +#[cfg(windows)] +use std::cmp::Ordering; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::fixed::FixedOffset; +use super::{MappedLocalTime, TimeZone}; +#[allow(deprecated)] +use crate::Date; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::{DateTime, Utc}; + +#[cfg(unix)] +#[path = "unix.rs"] +mod inner; + +#[cfg(windows)] +#[path = "windows.rs"] +mod inner; + +#[cfg(all(windows, feature = "clock"))] +#[allow(unreachable_pub)] +mod win_bindings; + +#[cfg(all(any(target_os = "android", target_env = "ohos", test), feature = "clock"))] +mod tz_data; + +#[cfg(all( + not(unix), + not(windows), + not(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )) +))] +mod inner { + use crate::{FixedOffset, MappedLocalTime, NaiveDateTime}; + + pub(super) fn offset_from_utc_datetime( + _utc_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) + } + + pub(super) fn offset_from_local_datetime( + _local_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) + } +} + +#[cfg(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) +))] +mod inner { + use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike}; + + pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset(); + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + } + + pub(super) fn offset_from_local_datetime( + local: &NaiveDateTime, + ) -> MappedLocalTime { + let mut year = local.year(); + if year < 100 { + // The API in `js_sys` does not let us create a `Date` with negative years. + // And values for years from `0` to `99` map to the years `1900` to `1999`. + // Shift the value by a multiple of 400 years until it is `>= 100`. + let shift_cycles = (year - 100).div_euclid(400); + year -= shift_cycles * 400; + } + let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec( + year as u32, + local.month0() as i32, + local.day() as i32, + local.hour() as i32, + local.minute() as i32, + local.second() as i32, + // ignore milliseconds, our representation of leap seconds may be problematic + ); + let offset = js_date.get_timezone_offset(); + // We always get a result, even if this time does not exist or is ambiguous. + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + } +} + +#[cfg(unix)] +mod tz_info; + +/// The local timescale. +/// +/// Using the [`TimeZone`](./trait.TimeZone.html) methods +/// on the Local struct is the preferred way to construct `DateTime` +/// instances. +/// +/// # Example +/// +/// ``` +/// use chrono::{DateTime, Local, TimeZone}; +/// +/// let dt1: DateTime = Local::now(); +/// let dt2: DateTime = Local.timestamp_opt(0, 0).unwrap(); +/// assert!(dt1 >= dt2); +/// ``` +#[derive(Copy, Clone, Debug)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Local; + +impl Local { + /// Returns a `Date` which corresponds to the current date. + #[deprecated(since = "0.4.23", note = "use `Local::now()` instead")] + #[allow(deprecated)] + #[must_use] + pub fn today() -> Date { + Local::now().date() + } + + /// Returns a `DateTime` which corresponds to the current date, time and offset from + /// UTC. + /// + /// See also the similar [`Utc::now()`] which returns `DateTime`, i.e. without the local + /// offset. + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{DateTime, FixedOffset, Local}; + /// // Current local time + /// let now = Local::now(); + /// + /// // Current local date + /// let today = now.date_naive(); + /// + /// // Current local time, converted to `DateTime` + /// let now_fixed_offset = Local::now().fixed_offset(); + /// // or + /// let now_fixed_offset: DateTime = Local::now().into(); + /// + /// // Current time in some timezone (let's use +05:00) + /// // Note that it is usually more efficient to use `Utc::now` for this use case. + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Local::now().with_timezone(&offset); + /// ``` + pub fn now() -> DateTime { + Utc::now().with_timezone(&Local) + } +} + +impl TimeZone for Local { + type Offset = FixedOffset; + + fn from_offset(_offset: &FixedOffset) -> Local { + Local + } + + #[allow(deprecated)] + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime { + // Get the offset at local midnight. + self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) + } + + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime { + inner::offset_from_local_datetime(local) + } + + #[allow(deprecated)] + fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { + // Get the offset at midnight. + self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN)) + } + + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + inner::offset_from_utc_datetime(utc).unwrap() + } +} + +#[cfg(windows)] +#[derive(Copy, Clone, Eq, PartialEq)] +struct Transition { + transition_utc: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, +} + +#[cfg(windows)] +impl Transition { + fn new( + transition_local: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, + ) -> Transition { + // It is no problem if the transition time in UTC falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let transition_utc = transition_local.overflowing_sub_offset(offset_before); + Transition { transition_utc, offset_before, offset_after } + } +} + +#[cfg(windows)] +impl PartialOrd for Transition { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(windows)] +impl Ord for Transition { + fn cmp(&self, other: &Self) -> Ordering { + self.transition_utc.cmp(&other.transition_utc) + } +} + +// Calculate the time in UTC given a local time and transitions. +// `transitions` must be sorted. +#[cfg(windows)] +fn lookup_with_dst_transitions( + transitions: &[Transition], + dt: NaiveDateTime, +) -> MappedLocalTime { + for t in transitions.iter() { + // A transition can result in the wall clock time going forward (creating a gap) or going + // backward (creating a fold). We are interested in the earliest and latest wall time of the + // transition, as this are the times between which `dt` does may not exist or is ambiguous. + // + // It is no problem if the transition times falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let (offset_min, offset_max) = + match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() { + true => (t.offset_before, t.offset_after), + false => (t.offset_after, t.offset_before), + }; + let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min); + let wall_latest = t.transition_utc.overflowing_add_offset(offset_max); + + if dt < wall_earliest { + return MappedLocalTime::Single(t.offset_before); + } else if dt <= wall_latest { + return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) { + Ordering::Equal => MappedLocalTime::Single(t.offset_before), + Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after), + Ordering::Greater => { + if dt == wall_earliest { + MappedLocalTime::Single(t.offset_before) + } else if dt == wall_latest { + MappedLocalTime::Single(t.offset_after) + } else { + MappedLocalTime::None + } + } + }; + } + } + MappedLocalTime::Single(transitions.last().unwrap().offset_after) +} + +#[cfg(test)] +mod tests { + use super::Local; + use crate::offset::TimeZone; + #[cfg(windows)] + use crate::offset::local::{Transition, lookup_with_dst_transitions}; + use crate::{Datelike, Days, Utc}; + #[cfg(windows)] + use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime}; + + #[test] + fn verify_correct_offsets() { + let now = Local::now(); + let from_local = Local.from_local_datetime(&now.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&now.naive_utc()); + + assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(now, from_local); + assert_eq!(now, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_past() { + let distant_past = Local::now() - Days::new(365 * 500); + let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); + + assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_past, from_local); + assert_eq!(distant_past, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_future() { + let distant_future = Local::now() + Days::new(365 * 35000); + let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); + + assert_eq!( + distant_future.offset().local_minus_utc(), + from_local.offset().local_minus_utc() + ); + assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_future, from_local); + assert_eq!(distant_future, from_utc); + } + + #[test] + fn test_local_date_sanity_check() { + // issue #27 + assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28); + } + + #[test] + fn test_leap_second() { + // issue #123 + let today = Utc::now().date_naive(); + + if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) { + let timestr = dt.time().to_string(); + // the OS API may or may not support the leap second, + // but there are only two sensible options. + assert!( + timestr == "15:02:60" || timestr == "15:03:00", + "unexpected timestr {timestr:?}" + ); + } + + if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) { + let timestr = dt.time().to_string(); + assert!( + timestr == "15:02:03.234" || timestr == "15:02:04.234", + "unexpected timestr {timestr:?}" + ); + } + } + + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions() { + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }; + + #[track_caller] + #[allow(clippy::too_many_arguments)] + fn compare_lookup( + transitions: &[Transition], + y: i32, + m: u32, + d: u32, + h: u32, + n: u32, + s: u32, + result: MappedLocalTime, + ) { + let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + assert_eq!(lookup_with_dst_transitions(transitions, dt), result); + } + + // dst transition before std transition + // dst offset > std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std)); + + // std transition before dst transition + // dst offset > std offset + let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst)); + + // dst transition before std transition + // dst offset < std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); + + // std transition before dst transition + // dst offset < std offset + let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap(); + let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + + // offset stays the same + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); + + // single transition + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + } + + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions_limits() { + // Transition beyond UTC year end doesn't panic in year of `NaiveDate::MAX` + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst), + Transition::new(NaiveDateTime::MAX, dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()), + MappedLocalTime::Single(std) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()), + MappedLocalTime::Single(dst) + ); + // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX), + MappedLocalTime::Ambiguous(dst, std) + ); + + // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN` + let std = FixedOffset::west_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MIN, std, dst), + Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()), + MappedLocalTime::Single(dst) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()), + MappedLocalTime::Single(std) + ); + // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN), + MappedLocalTime::Ambiguous(std, dst) + ); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let local = Local; + // Local is a ZST and serializes to 0 bytes + let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap(); + assert_eq!(bytes.len(), 0); + + // but is deserialized to an archived variant without a + // wrapping object + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), super::ArchivedLocal); + } +} diff --git a/src/offset/local/tz_data.rs b/src/offset/local/tz_data.rs new file mode 100644 index 0000000000..fd6d2e87b9 --- /dev/null +++ b/src/offset/local/tz_data.rs @@ -0,0 +1,291 @@ +//! Rust parser of ZoneInfoDb(`tzdata`) on Android and OpenHarmony +//! +//! Ported from: https://android.googlesource.com/platform/prebuilts/fullsdk/sources/+/refs/heads/androidx-appcompat-release/android-34/com/android/i18n/timezone/ZoneInfoDb.java +use std::{ + ffi::CStr, + fmt::Debug, + fs::File, + io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}, +}; + +/// Get timezone data from the `tzdata` file of HarmonyOS NEXT. +#[cfg(target_env = "ohos")] +pub(crate) fn for_zone(tz_string: &str) -> Result>> { + let mut file = File::open("/system/etc/zoneinfo/tzdata")?; + find_tz_data::(&mut file, tz_string.as_bytes()) +} + +/// Get timezone data from the `tzdata` file of Android. +#[cfg(target_os = "android")] +pub(crate) fn for_zone(tz_string: &str) -> Result>> { + let mut file = open_android_tz_data_file()?; + find_tz_data::(&mut file, tz_string.as_bytes()) +} + +/// Open the `tzdata` file of Android from the environment variables. +#[cfg(target_os = "android")] +fn open_android_tz_data_file() -> Result { + for (env_var, path) in [ + // Default root path: /apex/com.android.tzdata + ("ANDROID_TZDATA_ROOT", "/etc/tz"), + // Default root path: /data + ("ANDROID_DATA", "/misc/zoneinfo/current"), + // Default root path: /system + ("ANDROID_ROOT", "/usr/share/zoneinfo"), + ] { + if let Ok(env_value) = std::env::var(env_var) { + if let Ok(file) = File::open(format!("{}{}/tzdata", env_value, path)) { + return Ok(file); + } + } + } + Err(Error::from(ErrorKind::NotFound)) +} + +/// Get timezone data from the `tzdata` file reader +#[cfg(any(test, target_env = "ohos", target_os = "android"))] +fn find_tz_data( + mut reader: impl Read + Seek, + tz_name: &[u8], +) -> Result>> { + let header = TzDataHeader::new(&mut reader)?; + let index = TzDataIndexes::new::(&mut reader, &header)?; + Ok(if let Some(entry) = index.find_timezone(tz_name) { + Some(index.find_tzdata(reader, &header, entry)?) + } else { + None + }) +} + +/// Header of the `tzdata` file. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct TzDataHeader { + version: [u8; 5], + index_offset: u32, + data_offset: u32, + zonetab_offset: u32, +} + +impl TzDataHeader { + /// Parse the header of the `tzdata` file. + fn new(mut data: impl Read) -> Result { + let version = { + let mut magic = [0; TZDATA_VERSION_LEN]; + data.read_exact(&mut magic)?; + if !magic.starts_with(b"tzdata") || magic[TZDATA_VERSION_LEN - 1] != 0 { + return Err(Error::new(ErrorKind::Other, "invalid tzdata header magic")); + } + let mut version = [0; 5]; + version.copy_from_slice(&magic[6..11]); + version + }; + + let mut offset = [0; 4]; + data.read_exact(&mut offset)?; + let index_offset = u32::from_be_bytes(offset); + data.read_exact(&mut offset)?; + let data_offset = u32::from_be_bytes(offset); + data.read_exact(&mut offset)?; + let zonetab_offset = u32::from_be_bytes(offset); + + Ok(Self { version, index_offset, data_offset, zonetab_offset }) + } +} + +/// Indexes of the `tzdata` file. +struct TzDataIndexes { + indexes: Vec, +} + +impl TzDataIndexes { + /// Create a new `TzDataIndexes` from the `tzdata` file reader. + fn new(mut reader: impl Read, header: &TzDataHeader) -> Result { + let mut buf = vec![0; header.data_offset.saturating_sub(header.index_offset) as usize]; + reader.read_exact(&mut buf)?; + // replace chunks_exact with array_chunks when it's stable. A trailing partial chunk + // (when the index region size is not a multiple of `ENTRY_LEN`) is not a valid entry + // and must be skipped, otherwise the slicing below reads out of bounds. + Ok(TzDataIndexes { + indexes: buf + .chunks_exact(ENTRY_LEN) + .filter_map(|chunk| { + from_bytes_until_nul(&chunk[..TZ_NAME_LEN]).map(|name| { + let name = name.to_bytes().to_vec().into_boxed_slice(); + let offset = u32::from_be_bytes( + chunk[TZ_NAME_LEN..TZ_NAME_LEN + 4].try_into().unwrap(), + ); + let length = u32::from_be_bytes( + chunk[TZ_NAME_LEN + 4..TZ_NAME_LEN + 8].try_into().unwrap(), + ); + TzDataIndex { name, offset, length } + }) + }) + .collect(), + }) + } + + /// Find a timezone by name. + fn find_timezone(&self, timezone: &[u8]) -> Option<&TzDataIndex> { + // timezones in tzdata are sorted by name. + self.indexes.binary_search_by_key(&timezone, |x| &x.name).map(|x| &self.indexes[x]).ok() + } + + /// Retrieve a chunk of timezone data by the index. + fn find_tzdata( + &self, + mut reader: impl Read + Seek, + header: &TzDataHeader, + index: &TzDataIndex, + ) -> Result> { + reader.seek(SeekFrom::Start(index.offset as u64 + header.data_offset as u64))?; + let mut buffer = vec![0; index.length as usize]; + reader.read_exact(&mut buffer)?; + Ok(buffer) + } +} + +/// Index entry of the `tzdata` file. +struct TzDataIndex { + name: Box<[u8]>, + offset: u32, + length: u32, +} + +/// TODO: Change this `CStr::from_bytes_until_nul` once MSRV was bumped above 1.72.0 +fn from_bytes_until_nul(bytes: &[u8]) -> Option<&CStr> { + let nul_pos = bytes.iter().position(|&b| b == 0)?; + // SAFETY: + // 1. nul_pos + 1 <= bytes.len() + // 2. We know there is a nul byte at nul_pos, so this slice (ending at the nul byte) is a well-formed C string. + Some(unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..=nul_pos]) }) +} + +/// Ohos tzdata index entry size: `name + offset + length` +#[cfg(any(test, target_env = "ohos"))] +const OHOS_ENTRY_LEN: usize = TZ_NAME_LEN + 2 * size_of::(); +/// Android tzdata index entry size: `name + offset + length + raw_utc_offset(legacy)`: +/// [reference](https://android.googlesource.com/platform/prebuilts/fullsdk/sources/+/refs/heads/androidx-appcompat-release/android-34/com/android/i18n/timezone/ZoneInfoDb.java#271) +#[cfg(any(test, target_os = "android"))] +const ANDROID_ENTRY_LEN: usize = TZ_NAME_LEN + 3 * size_of::(); +/// The database reserves 40 bytes for each id. +const TZ_NAME_LEN: usize = 40; +/// Size of the version string in the header of `tzdata` file. +/// e.g. `tzdata2024b\0` +const TZDATA_VERSION_LEN: usize = 12; + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_tzdata_index_partial_entry() { + // An index region whose size is not a multiple of `ENTRY_LEN` leaves a trailing + // partial chunk that is too short to slice as an entry. + let mut data = Vec::new(); + data.extend_from_slice(b"tzdata2024a\0"); + data.extend_from_slice(&24u32.to_be_bytes()); // index_offset + data.extend_from_slice(&44u32.to_be_bytes()); // data_offset, index region = 20 bytes + data.extend_from_slice(&100u32.to_be_bytes()); // zonetab_offset + data.extend_from_slice(&[0u8; 20]); + let header = TzDataHeader::new(&mut Cursor::new(&data[..])).unwrap(); + let indexes = + TzDataIndexes::new::(&mut Cursor::new(&data[24..]), &header).unwrap(); + assert!(indexes.indexes.is_empty()); + } + + #[test] + fn test_ohos_tzdata_header_and_index() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + assert_eq!(header.version, *b"2024a"); + assert_eq!(header.index_offset, 24); + assert_eq!(header.data_offset, 21240); + assert_eq!(header.zonetab_offset, 272428); + + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + assert_eq!(iter.indexes.len(), 442); + assert!(iter.find_timezone(b"Asia/Shanghai").is_some()); + assert!(iter.find_timezone(b"Pacific/Noumea").is_some()); + } + + #[test] + fn test_ohos_tzdata_loading() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + let timezone = iter.find_timezone(b"Asia/Shanghai").unwrap(); + let tzdata = iter.find_tzdata(&file, &header, timezone).unwrap(); + assert_eq!(tzdata.len(), 393); + } + + #[test] + fn test_invalid_tzdata_header() { + TzDataHeader::new(&b"tzdaaa2024aaaaaaaaaaaaaaa\0"[..]).unwrap_err(); + } + + #[test] + fn test_android_tzdata_header_and_index() { + let file = File::open("./tests/android/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + assert_eq!(header.version, *b"2021a"); + assert_eq!(header.index_offset, 24); + assert_eq!(header.data_offset, 30860); + assert_eq!(header.zonetab_offset, 491837); + + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + assert_eq!(iter.indexes.len(), 593); + assert!(iter.find_timezone(b"Asia/Shanghai").is_some()); + assert!(iter.find_timezone(b"Pacific/Noumea").is_some()); + } + + #[test] + fn test_android_tzdata_loading() { + let file = File::open("./tests/android/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + let timezone = iter.find_timezone(b"Asia/Shanghai").unwrap(); + let tzdata = iter.find_tzdata(&file, &header, timezone).unwrap(); + assert_eq!(tzdata.len(), 573); + } + + #[test] + fn test_ohos_tzdata_find() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let tzdata = find_tz_data::(file, b"Asia/Shanghai").unwrap().unwrap(); + assert_eq!(tzdata.len(), 393); + } + + #[test] + fn test_ohos_tzdata_find_missing() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + assert!(find_tz_data::(file, b"Asia/Sjasdfai").unwrap().is_none()); + } + + #[test] + fn test_android_tzdata_find() { + let file = File::open("./tests/android/tzdata").unwrap(); + let tzdata = find_tz_data::(file, b"Asia/Shanghai").unwrap().unwrap(); + assert_eq!(tzdata.len(), 573); + } + + #[test] + fn test_android_tzdata_find_missing() { + let file = File::open("./tests/android/tzdata").unwrap(); + assert!(find_tz_data::(file, b"Asia/S000000i").unwrap().is_none()); + } + + #[cfg(target_env = "ohos")] + #[test] + fn test_ohos_machine_tz_data_loading() { + let tzdata = for_zone(b"Asia/Shanghai").unwrap().unwrap(); + assert!(!tzdata.is_empty()); + } + + #[cfg(target_os = "android")] + #[test] + fn test_android_machine_tz_data_loading() { + let tzdata = for_zone(b"Asia/Shanghai").unwrap().unwrap(); + assert!(!tzdata.is_empty()); + } +} diff --git a/src/offset/local/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs new file mode 100644 index 0000000000..a8e53010b3 --- /dev/null +++ b/src/offset/local/tz_info/mod.rs @@ -0,0 +1,116 @@ +#![deny(missing_docs)] +#![allow(dead_code)] +#![warn(unreachable_pub)] + +use std::num::ParseIntError; +use std::str::Utf8Error; +use std::time::SystemTimeError; +use std::{error, fmt, io}; + +mod timezone; +pub(crate) use timezone::TimeZone; + +mod parser; +mod rule; + +/// Unified error type for everything in the crate +#[derive(Debug)] +pub(crate) enum Error { + /// Date time error + DateTime(&'static str), + /// Local time type search error + FindLocalTimeType(&'static str), + /// Local time type error + LocalTimeType(&'static str), + /// Invalid slice for integer conversion + InvalidSlice(&'static str), + /// Invalid Tzif file + InvalidTzFile(&'static str), + /// Invalid TZ string + InvalidTzString(&'static str), + /// I/O error + Io(io::Error), + /// Out of range error + OutOfRange(&'static str), + /// Integer parsing error + ParseInt(ParseIntError), + /// Date time projection error + ProjectDateTime(&'static str), + /// System time error + SystemTime(SystemTimeError), + /// Time zone error + TimeZone(&'static str), + /// Transition rule error + TransitionRule(&'static str), + /// Unsupported Tzif file + UnsupportedTzFile(&'static str), + /// Unsupported TZ string + UnsupportedTzString(&'static str), + /// UTF-8 error + Utf8(Utf8Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + DateTime(error) => write!(f, "invalid date time: {error}"), + FindLocalTimeType(error) => error.fmt(f), + LocalTimeType(error) => write!(f, "invalid local time type: {error}"), + InvalidSlice(error) => error.fmt(f), + InvalidTzString(error) => write!(f, "invalid TZ string: {error}"), + InvalidTzFile(error) => error.fmt(f), + Io(error) => error.fmt(f), + OutOfRange(error) => error.fmt(f), + ParseInt(error) => error.fmt(f), + ProjectDateTime(error) => error.fmt(f), + SystemTime(error) => error.fmt(f), + TransitionRule(error) => write!(f, "invalid transition rule: {error}"), + TimeZone(error) => write!(f, "invalid time zone: {error}"), + UnsupportedTzFile(error) => error.fmt(f), + UnsupportedTzString(error) => write!(f, "unsupported TZ string: {error}"), + Utf8(error) => error.fmt(f), + } + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) + } +} + +impl From for Error { + fn from(error: ParseIntError) -> Self { + Error::ParseInt(error) + } +} + +impl From for Error { + fn from(error: SystemTimeError) -> Self { + Error::SystemTime(error) + } +} + +impl From for Error { + fn from(error: Utf8Error) -> Self { + Error::Utf8(error) + } +} + +/// Number of hours in one day +const HOURS_PER_DAY: i64 = 24; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of seconds in one day +const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY; +/// Number of days in one week +const DAYS_PER_WEEK: i64 = 7; + +/// Month days in a normal year +const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +/// Cumulated month days in a normal year +const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs new file mode 100644 index 0000000000..84c99063c1 --- /dev/null +++ b/src/offset/local/tz_info/parser.rs @@ -0,0 +1,348 @@ +use std::io::{self, ErrorKind}; +use std::iter; +use std::num::ParseIntError; +use std::str::{self, FromStr}; + +use super::Error; +use super::rule::TransitionRule; +use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition}; + +pub(super) fn parse(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + let state = State::new(&mut cursor, true)?; + let (state, footer) = match state.header.version { + Version::V1 => match cursor.is_empty() { + true => (state, None), + false => { + return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")); + } + }, + Version::V2 | Version::V3 => { + let state = State::new(&mut cursor, false)?; + (state, Some(cursor.remaining())) + } + }; + + let mut transitions = Vec::with_capacity(state.header.transition_count); + for (arr_time, &local_time_type_index) in + state.transition_times.chunks_exact(state.time_size).zip(state.transition_types) + { + let unix_leap_time = + state.parse_time(&arr_time[0..state.time_size], state.header.version)?; + let local_time_type_index = local_time_type_index as usize; + transitions.push(Transition::new(unix_leap_time, local_time_type_index)); + } + + let mut local_time_types = Vec::with_capacity(state.header.type_count); + for arr in state.local_time_types.chunks_exact(6) { + let ut_offset = read_be_i32(&arr[..4])?; + + let is_dst = match arr[4] { + 0 => false, + 1 => true, + _ => return Err(Error::InvalidTzFile("invalid DST indicator")), + }; + + let char_index = arr[5] as usize; + if char_index >= state.header.char_count { + return Err(Error::InvalidTzFile("invalid time zone name char index")); + } + + let position = match state.names[char_index..].iter().position(|&c| c == b'\0') { + Some(position) => position, + None => return Err(Error::InvalidTzFile("invalid time zone name char index")), + }; + + let name = &state.names[char_index..char_index + position]; + let name = if !name.is_empty() { Some(name) } else { None }; + local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?); + } + + let mut leap_seconds = Vec::with_capacity(state.header.leap_count); + for arr in state.leap_seconds.chunks_exact(state.time_size + 4) { + let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?; + let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?; + leap_seconds.push(LeapSecond::new(unix_leap_time, correction)); + } + + let std_walls_iter = state.std_walls.iter().copied().chain(iter::repeat(0)); + let ut_locals_iter = state.ut_locals.iter().copied().chain(iter::repeat(0)); + if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) { + return Err(Error::InvalidTzFile( + "invalid couple of standard/wall and UT/local indicators", + )); + } + + let extra_rule = match footer { + Some(footer) => { + let footer = str::from_utf8(footer)?; + if !(footer.starts_with('\n') && footer.ends_with('\n')) { + return Err(Error::InvalidTzFile("invalid footer")); + } + + let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace()); + if tz_string.starts_with(':') || tz_string.contains('\0') { + return Err(Error::InvalidTzFile("invalid footer")); + } + + match tz_string.is_empty() { + true => None, + false => Some(TransitionRule::from_tz_string( + tz_string.as_bytes(), + state.header.version == Version::V3, + )?), + } + } + None => None, + }; + + TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule) +} + +/// TZif data blocks +struct State<'a> { + header: Header, + /// Time size in bytes + time_size: usize, + /// Transition times data block + transition_times: &'a [u8], + /// Transition types data block + transition_types: &'a [u8], + /// Local time types data block + local_time_types: &'a [u8], + /// Time zone names data block + names: &'a [u8], + /// Leap seconds data block + leap_seconds: &'a [u8], + /// UT/local indicators data block + std_walls: &'a [u8], + /// Standard/wall indicators data block + ut_locals: &'a [u8], +} + +impl<'a> State<'a> { + /// Read TZif data blocks + fn new(cursor: &mut Cursor<'a>, first: bool) -> Result { + let header = Header::new(cursor)?; + let time_size = match first { + true => 4, // We always parse V1 first + false => 8, + }; + + Ok(Self { + time_size, + transition_times: cursor.read_exact(header.transition_count * time_size)?, + transition_types: cursor.read_exact(header.transition_count)?, + local_time_types: cursor.read_exact(header.type_count * 6)?, + names: cursor.read_exact(header.char_count)?, + leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?, + std_walls: cursor.read_exact(header.std_wall_count)?, + ut_locals: cursor.read_exact(header.ut_local_count)?, + header, + }) + } + + /// Parse time values + fn parse_time(&self, arr: &[u8], version: Version) -> Result { + match version { + Version::V1 => Ok(read_be_i32(&arr[..4])?.into()), + Version::V2 | Version::V3 => read_be_i64(arr), + } + } +} + +/// TZif header +#[derive(Debug)] +struct Header { + /// TZif version + version: Version, + /// Number of UT/local indicators + ut_local_count: usize, + /// Number of standard/wall indicators + std_wall_count: usize, + /// Number of leap-second records + leap_count: usize, + /// Number of transition times + transition_count: usize, + /// Number of local time type records + type_count: usize, + /// Number of time zone names bytes + char_count: usize, +} + +impl Header { + fn new(cursor: &mut Cursor) -> Result { + let magic = cursor.read_exact(4)?; + if magic != *b"TZif" { + return Err(Error::InvalidTzFile("invalid magic number")); + } + + let version = match cursor.read_exact(1)? { + [0x00] => Version::V1, + [0x32] => Version::V2, + [0x33] => Version::V3, + _ => return Err(Error::UnsupportedTzFile("unsupported TZif version")), + }; + + cursor.read_exact(15)?; + let ut_local_count = cursor.read_be_u32()?; + let std_wall_count = cursor.read_be_u32()?; + let leap_count = cursor.read_be_u32()?; + let transition_count = cursor.read_be_u32()?; + let type_count = cursor.read_be_u32()?; + let char_count = cursor.read_be_u32()?; + + if !(type_count != 0 + && char_count != 0 + && (ut_local_count == 0 || ut_local_count == type_count) + && (std_wall_count == 0 || std_wall_count == type_count)) + { + return Err(Error::InvalidTzFile("invalid header")); + } + + Ok(Self { + version, + ut_local_count: ut_local_count as usize, + std_wall_count: std_wall_count as usize, + leap_count: leap_count as usize, + transition_count: transition_count as usize, + type_count: type_count as usize, + char_count: char_count as usize, + }) + } +} + +/// A `Cursor` contains a slice of a buffer and a read count. +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Cursor<'a> { + /// Slice representing the remaining data to be read + remaining: &'a [u8], + /// Number of already read bytes + read_count: usize, +} + +impl<'a> Cursor<'a> { + /// Construct a new `Cursor` from remaining data + pub(crate) const fn new(remaining: &'a [u8]) -> Self { + Self { remaining, read_count: 0 } + } + + pub(crate) fn peek(&self) -> Option<&u8> { + self.remaining().first() + } + + /// Returns remaining data + pub(crate) const fn remaining(&self) -> &'a [u8] { + self.remaining + } + + /// Returns `true` if data is remaining + pub(crate) const fn is_empty(&self) -> bool { + self.remaining.is_empty() + } + + pub(crate) fn read_be_u32(&mut self) -> Result { + let mut buf = [0; 4]; + buf.copy_from_slice(self.read_exact(4)?); + Ok(u32::from_be_bytes(buf)) + } + + #[cfg(target_env = "ohos")] + pub(crate) fn seek_after(&mut self, offset: usize) -> Result { + if offset < self.read_count { + return Err(io::Error::from(ErrorKind::UnexpectedEof)); + } + match self.remaining.get((offset - self.read_count)..) { + Some(remaining) => { + self.remaining = remaining; + self.read_count = offset; + Ok(offset) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + + /// Read exactly `count` bytes, reducing remaining data and incrementing read count + pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> { + match (self.remaining.get(..count), self.remaining.get(count..)) { + (Some(result), Some(remaining)) => { + self.remaining = remaining; + self.read_count += count; + Ok(result) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + + /// Read bytes and compare them to the provided tag + pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> { + if self.read_exact(tag.len())? == tag { + Ok(()) + } else { + Err(io::Error::from(ErrorKind::InvalidData)) + } + } + + /// Read bytes if the remaining data is prefixed by the provided tag + pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result { + if self.remaining.starts_with(tag) { + self.read_exact(tag.len())?; + Ok(true) + } else { + Ok(false) + } + } + + /// Read bytes as long as the provided predicate is true + pub(crate) fn read_while bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(|x| !f(x)) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } + + // Parse an integer out of the ASCII digits + pub(crate) fn read_int>(&mut self) -> Result { + let bytes = self.read_while(u8::is_ascii_digit)?; + Ok(str::from_utf8(bytes)?.parse()?) + } + + /// Read bytes until the provided predicate is true + pub(crate) fn read_until bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(f) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } +} + +pub(crate) fn read_be_i32(bytes: &[u8]) -> Result { + if bytes.len() != 4 { + return Err(Error::InvalidSlice("too short for i32")); + } + + let mut buf = [0; 4]; + buf.copy_from_slice(bytes); + Ok(i32::from_be_bytes(buf)) +} + +pub(crate) fn read_be_i64(bytes: &[u8]) -> Result { + if bytes.len() != 8 { + return Err(Error::InvalidSlice("too short for i64")); + } + + let mut buf = [0; 8]; + buf.copy_from_slice(bytes); + Ok(i64::from_be_bytes(buf)) +} + +/// TZif version +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Version { + /// Version 1 + V1, + /// Version 2 + V2, + /// Version 3 + V3, +} diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs new file mode 100644 index 0000000000..19159f62da --- /dev/null +++ b/src/offset/local/tz_info/rule.rs @@ -0,0 +1,1048 @@ +use super::parser::Cursor; +use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; +use super::{ + CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, Error, + SECONDS_PER_DAY, +}; +use crate::{Datelike, NaiveDateTime}; +use std::cmp::Ordering; + +/// Transition rule +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) enum TransitionRule { + /// Fixed local time type + Fixed(LocalTimeType), + /// Alternate local time types + Alternate(AlternateTime), +} + +impl TransitionRule { + /// Parse a POSIX TZ string containing a time zone description, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + /// + /// TZ string extensions from [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536#section-3.3.1) may be used. + pub(super) fn from_tz_string( + tz_string: &[u8], + use_string_extensions: bool, + ) -> Result { + let mut cursor = Cursor::new(tz_string); + + let std_time_zone = Some(parse_name(&mut cursor)?); + let std_offset = parse_offset(&mut cursor)?; + + if cursor.is_empty() { + return Ok(LocalTimeType::new(-std_offset, false, std_time_zone)?.into()); + } + + let dst_time_zone = Some(parse_name(&mut cursor)?); + + let dst_offset = match cursor.peek() { + Some(&b',') => std_offset - 3600, + Some(_) => parse_offset(&mut cursor)?, + None => { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); + } + }; + + if cursor.is_empty() { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); + } + + cursor.read_tag(b",")?; + let (dst_start, dst_start_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + cursor.read_tag(b",")?; + let (dst_end, dst_end_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + if !cursor.is_empty() { + return Err(Error::InvalidTzString("remaining data after parsing TZ string")); + } + + Ok(AlternateTime::new( + LocalTimeType::new(-std_offset, false, std_time_zone)?, + LocalTimeType::new(-dst_offset, true, dst_time_zone)?, + dst_start, + dst_start_time, + dst_end, + dst_end_time, + )? + .into()) + } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + match self { + TransitionRule::Fixed(local_time_type) => Ok(local_time_type), + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type(unix_time) + } + } + } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + match self { + TransitionRule::Fixed(local_time_type) => { + Ok(crate::MappedLocalTime::Single(*local_time_type)) + } + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type_from_local(local_time) + } + } + } +} + +impl From for TransitionRule { + fn from(inner: LocalTimeType) -> Self { + TransitionRule::Fixed(inner) + } +} + +impl From for TransitionRule { + fn from(inner: AlternateTime) -> Self { + TransitionRule::Alternate(inner) + } +} + +/// Transition rule representing alternate local time types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct AlternateTime { + /// Local time type for standard time + pub(super) std: LocalTimeType, + /// Local time type for Daylight Saving Time + pub(super) dst: LocalTimeType, + /// Start day of Daylight Saving Time + dst_start: RuleDay, + /// Local start day time of Daylight Saving Time, in seconds + dst_start_time: i32, + /// End day of Daylight Saving Time + dst_end: RuleDay, + /// Local end day time of Daylight Saving Time, in seconds + dst_end_time: i32, +} + +impl AlternateTime { + /// Construct a transition rule representing alternate local time types + const fn new( + std: LocalTimeType, + dst: LocalTimeType, + dst_start: RuleDay, + dst_start_time: i32, + dst_end: RuleDay, + dst_end_time: i32, + ) -> Result { + // Overflow is not possible + if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK + && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) + { + return Err(Error::TransitionRule("invalid DST start or end time")); + } + + Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time }) + } + + /// Find the local time type associated to the alternate transition rule at the specified Unix time in seconds + fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + // Overflow is not possible + let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; + let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; + + let current_year = { + let dt = UtcDateTime::from_timespec(unix_time)?; + dt.year + }; + + // Check if the current year is valid for the following computations + if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) { + return Err(Error::OutOfRange("out of range date time")); + } + + let current_year_dst_start_unix_time = + self.dst_start.unix_time(current_year, dst_start_time_in_utc); + let current_year_dst_end_unix_time = + self.dst_end.unix_time(current_year, dst_end_time_in_utc); + + // Check DST start/end Unix times for previous/current/next years to support for transition day times outside of [0h, 24h] range + let is_dst = + match Ord::cmp(¤t_year_dst_start_unix_time, ¤t_year_dst_end_unix_time) { + Ordering::Less | Ordering::Equal => { + if unix_time < current_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + if unix_time < previous_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + previous_year_dst_start_unix_time <= unix_time + } else { + false + } + } else if unix_time < current_year_dst_end_unix_time { + true + } else { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + if next_year_dst_start_unix_time <= unix_time { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + unix_time < next_year_dst_end_unix_time + } else { + false + } + } + } + Ordering::Greater => { + if unix_time < current_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + if unix_time < previous_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + unix_time < previous_year_dst_end_unix_time + } else { + true + } + } else if unix_time < current_year_dst_start_unix_time { + false + } else { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + if next_year_dst_end_unix_time <= unix_time { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + next_year_dst_start_unix_time <= unix_time + } else { + true + } + } + } + }; + + if is_dst { Ok(&self.dst) } else { Ok(&self.std) } + } + + fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + // Year must be between i32::MIN + 2 and i32::MAX - 2, year in NaiveDate is always smaller. + let current_year = local_time.year(); + let local_time = local_time.and_utc().timestamp(); + + let dst_start_transition_start = + self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time); + let dst_start_transition_end = self.dst_start.unix_time(current_year, 0) + + i64::from(self.dst_start_time) + + i64::from(self.dst.ut_offset) + - i64::from(self.std.ut_offset); + + let dst_end_transition_start = + self.dst_end.unix_time(current_year, 0) + i64::from(self.dst_end_time); + let dst_end_transition_end = self.dst_end.unix_time(current_year, 0) + + i64::from(self.dst_end_time) + + i64::from(self.std.ut_offset) + - i64::from(self.dst.ut_offset); + + match self.std.ut_offset.cmp(&self.dst.ut_offset) { + Ordering::Equal => Ok(crate::MappedLocalTime::Single(self.std)), + Ordering::Less => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // northern hemisphere + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_start_transition_start { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::None) + } else if local_time >= dst_start_transition_end + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) + } else { + Ok(crate::MappedLocalTime::Single(self.std)) + } + } else { + // southern hemisphere regular DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_end_transition_end { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) + } else if local_time > dst_end_transition_end + && local_time < dst_start_transition_start + { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::None) + } else { + Ok(crate::MappedLocalTime::Single(self.dst)) + } + } + } + Ordering::Greater => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // southern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_start_transition_end { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_end_transition_start + { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::None) + } else { + Ok(crate::MappedLocalTime::Single(self.std)) + } + } else { + // northern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_end_transition_start { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time > dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::None) + } else if local_time >= dst_end_transition_end + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) + } else { + Ok(crate::MappedLocalTime::Single(self.dst)) + } + } + } + } + } +} + +/// Parse time zone name +fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> { + match cursor.peek() { + Some(b'<') => {} + _ => return Ok(cursor.read_while(u8::is_ascii_alphabetic)?), + } + + cursor.read_exact(1)?; + let unquoted = cursor.read_until(|&x| x == b'>')?; + cursor.read_exact(1)?; + Ok(unquoted) +} + +/// Parse time zone offset +fn parse_offset(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + // The offset must fit in a `FixedOffset`, which is limited to ±23:59:59. + if !(0..=23).contains(&hour) { + return Err(Error::InvalidTzString("invalid offset hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid offset minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid offset second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse transition rule time +fn parse_rule_time(cursor: &mut Cursor) -> Result { + let (hour, minute, second) = parse_hhmmss(cursor)?; + + if !(0..=24).contains(&hour) { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(hour * 3600 + minute * 60 + second) +} + +/// Parse transition rule time with TZ string extensions +fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + if !(-167..=167).contains(&hour) { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse hours, minutes and seconds +fn parse_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32), Error> { + let hour = cursor.read_int()?; + + let mut minute = 0; + let mut second = 0; + + if cursor.read_optional_tag(b":")? { + minute = cursor.read_int()?; + + if cursor.read_optional_tag(b":")? { + second = cursor.read_int()?; + } + } + + Ok((hour, minute, second)) +} + +/// Parse signed hours, minutes and seconds +fn parse_signed_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32, i32), Error> { + let mut sign = 1; + if let Some(&c) = cursor.peek() { + if c == b'+' || c == b'-' { + cursor.read_exact(1)?; + if c == b'-' { + sign = -1; + } + } + } + + let (hour, minute, second) = parse_hhmmss(cursor)?; + Ok((sign, hour, minute, second)) +} + +/// Transition rule day +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum RuleDay { + /// Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + Julian1WithoutLeap(u16), + /// Zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + Julian0WithLeap(u16), + /// Day represented by a month, a month week and a week day + MonthWeekday { + /// Month in `[1, 12]` + month: u8, + /// Week of the month in `[1, 5]`, with `5` representing the last week of the month + week: u8, + /// Day of the week in `[0, 6]` from Sunday + week_day: u8, + }, +} + +impl RuleDay { + /// Parse transition rule + fn parse(cursor: &mut Cursor, use_string_extensions: bool) -> Result<(Self, i32), Error> { + let date = match cursor.peek() { + Some(b'M') => { + cursor.read_exact(1)?; + let month = cursor.read_int()?; + cursor.read_tag(b".")?; + let week = cursor.read_int()?; + cursor.read_tag(b".")?; + let week_day = cursor.read_int()?; + RuleDay::month_weekday(month, week, week_day)? + } + Some(b'J') => { + cursor.read_exact(1)?; + RuleDay::julian_1(cursor.read_int()?)? + } + _ => RuleDay::julian_0(cursor.read_int()?)?, + }; + + Ok(( + date, + match (cursor.read_optional_tag(b"/")?, use_string_extensions) { + (false, _) => 2 * 3600, + (true, true) => parse_rule_time_extended(cursor)?, + (true, false) => parse_rule_time(cursor)?, + }, + )) + } + + /// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + fn julian_1(julian_day_1: u16) -> Result { + if !(1..=365).contains(&julian_day_1) { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian1WithoutLeap(julian_day_1)) + } + + /// Construct a transition rule day represented by a zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + const fn julian_0(julian_day_0: u16) -> Result { + if julian_day_0 > 365 { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian0WithLeap(julian_day_0)) + } + + /// Construct a transition rule day represented by a month, a month week and a week day + fn month_weekday(month: u8, week: u8, week_day: u8) -> Result { + if !(1..=12).contains(&month) { + return Err(Error::TransitionRule("invalid rule day month")); + } + + if !(1..=5).contains(&week) { + return Err(Error::TransitionRule("invalid rule day week")); + } + + if week_day > 6 { + return Err(Error::TransitionRule("invalid rule day week day")); + } + + Ok(RuleDay::MonthWeekday { month, week, week_day }) + } + + /// Get the transition date for the provided year + /// + /// ## Outputs + /// + /// * `month`: Month in `[1, 12]` + /// * `month_day`: Day of the month in `[1, 31]` + fn transition_date(&self, year: i32) -> (usize, i64) { + match *self { + RuleDay::Julian1WithoutLeap(year_day) => { + let year_day = year_day as i64; + + let month = match CUMUL_DAY_IN_MONTHS_NORMAL_YEAR.binary_search(&(year_day - 1)) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = year_day - CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + + (month, month_day) + } + RuleDay::Julian0WithLeap(year_day) => { + let leap = is_leap_year(year) as i64; + + let cumul_day_in_months = [ + 0, + 31, + 59 + leap, + 90 + leap, + 120 + leap, + 151 + leap, + 181 + leap, + 212 + leap, + 243 + leap, + 273 + leap, + 304 + leap, + 334 + leap, + ]; + + let year_day = year_day as i64; + + let month = match cumul_day_in_months.binary_search(&year_day) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = 1 + year_day - cumul_day_in_months[month - 1]; + + (month, month_day) + } + RuleDay::MonthWeekday { month: rule_month, week, week_day } => { + let leap = is_leap_year(year) as i64; + + let month = rule_month as usize; + + let mut day_in_month = DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + if month == 2 { + day_in_month += leap; + } + + let week_day_of_first_month_day = + (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK); + let first_week_day_occurrence_in_month = + 1 + (week_day as i64 - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK); + + let mut month_day = + first_week_day_occurrence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; + if month_day > day_in_month { + month_day -= DAYS_PER_WEEK + } + + (month, month_day) + } + } + } + + /// Returns the UTC Unix time in seconds associated to the transition date for the provided year + fn unix_time(&self, year: i32, day_time_in_utc: i64) -> i64 { + let (month, month_day) = self.transition_date(year); + days_since_unix_epoch(year, month, month_day) * SECONDS_PER_DAY + day_time_in_utc + } +} + +/// UTC date time exprimed in the [proleptic gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct UtcDateTime { + /// Year + pub(crate) year: i32, + /// Month in `[1, 12]` + pub(crate) month: u8, + /// Day of the month in `[1, 31]` + pub(crate) month_day: u8, + /// Hours since midnight in `[0, 23]` + pub(crate) hour: u8, + /// Minutes in `[0, 59]` + pub(crate) minute: u8, + /// Seconds in `[0, 60]`, with a possible leap second + pub(crate) second: u8, +} + +impl UtcDateTime { + /// Construct a UTC date time from a Unix time in seconds and nanoseconds + pub(crate) fn from_timespec(unix_time: i64) -> Result { + let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) { + Some(seconds) => seconds, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + let mut remaining_days = seconds / SECONDS_PER_DAY; + let mut remaining_seconds = seconds % SECONDS_PER_DAY; + if remaining_seconds < 0 { + remaining_seconds += SECONDS_PER_DAY; + remaining_days -= 1; + } + + let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS; + remaining_days %= DAYS_PER_400_YEARS; + if remaining_days < 0 { + remaining_days += DAYS_PER_400_YEARS; + cycles_400_years -= 1; + } + + let cycles_100_years = Ord::min(remaining_days / DAYS_PER_100_YEARS, 3); + remaining_days -= cycles_100_years * DAYS_PER_100_YEARS; + + let cycles_4_years = Ord::min(remaining_days / DAYS_PER_4_YEARS, 24); + remaining_days -= cycles_4_years * DAYS_PER_4_YEARS; + + let remaining_years = Ord::min(remaining_days / DAYS_PER_NORMAL_YEAR, 3); + remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR; + + let mut year = OFFSET_YEAR + + remaining_years + + cycles_4_years * 4 + + cycles_100_years * 100 + + cycles_400_years * 400; + + let mut month = 0; + while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() { + let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month]; + if remaining_days < days { + break; + } + remaining_days -= days; + month += 1; + } + month += 2; + + if month >= MONTHS_PER_YEAR as usize { + month -= MONTHS_PER_YEAR as usize; + year += 1; + } + month += 1; + + let month_day = 1 + remaining_days; + + let hour = remaining_seconds / SECONDS_PER_HOUR; + let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; + let second = remaining_seconds % SECONDS_PER_MINUTE; + + let year = match year >= i32::MIN as i64 && year <= i32::MAX as i64 { + true => year as i32, + false => return Err(Error::OutOfRange("i64 is out of range for i32")), + }; + + Ok(Self { + year, + month: month as u8, + month_day: month_day as u8, + hour: hour as u8, + minute: minute as u8, + second: second as u8, + }) + } +} + +/// Number of nanoseconds in one second +const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000; +/// Number of seconds in one minute +const SECONDS_PER_MINUTE: i64 = 60; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of minutes in one hour +const MINUTES_PER_HOUR: i64 = 60; +/// Number of months in one year +const MONTHS_PER_YEAR: i64 = 12; +/// Number of days in a normal year +const DAYS_PER_NORMAL_YEAR: i64 = 365; +/// Number of days in 4 years (including 1 leap year) +const DAYS_PER_4_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 4 + 1; +/// Number of days in 100 years (including 24 leap years) +const DAYS_PER_100_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 100 + 24; +/// Number of days in 400 years (including 97 leap years) +const DAYS_PER_400_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 400 + 97; +/// Unix time at `2000-03-01T00:00:00Z` (Wednesday) +const UNIX_OFFSET_SECS: i64 = 951868800; +/// Offset year +const OFFSET_YEAR: i64 = 2000; +/// Month days in a leap year from March +const DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH: [i64; 12] = + [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + +/// Compute the number of days since Unix epoch (`1970-01-01T00:00:00Z`). +/// +/// ## Inputs +/// +/// * `year`: Year +/// * `month`: Month in `[1, 12]` +/// * `month_day`: Day of the month in `[1, 31]` +pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 { + let is_leap_year = is_leap_year(year); + + let year = year as i64; + + let mut result = (year - 1970) * 365; + + if year >= 1970 { + result += (year - 1968) / 4; + result -= (year - 1900) / 100; + result += (year - 1600) / 400; + + if is_leap_year && month < 3 { + result -= 1; + } + } else { + result += (year - 1972) / 4; + result -= (year - 2000) / 100; + result += (year - 2000) / 400; + + if is_leap_year && month >= 3 { + result += 1; + } + } + + result += CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1; + + result +} + +/// Check if a year is a leap year +pub(crate) const fn is_leap_year(year: i32) -> bool { + year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) +} + +#[cfg(test)] +mod tests { + use super::super::timezone::Transition; + use super::super::{Error, TimeZone}; + use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule}; + + #[test] + fn test_quoted() -> Result<(), Error> { + let transition_rule = TransitionRule::from_tz_string(b"<-03>+3<+03>-3,J1,J365", false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(10800, true, Some(b"+03"))?, + RuleDay::julian_1(1)?, + 7200, + RuleDay::julian_1(365)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_full() -> Result<(), Error> { + let tz_string = b"NZST-12:00:00NZDT-13:00:00,M10.1.0/02:00:00,M3.3.0/02:00:00"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_dst() -> Result<(), Error> { + let tz_string = b"IST-1GMT0,M10.5.0,M3.5.0/1"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_hour() -> Result<(), Error> { + let tz_string = b"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_invalid_offset_hour() { + // An offset of 24 hours does not fit in a `FixedOffset` and must be rejected. + assert!(TransitionRule::from_tz_string(b"FOO24", false).is_err()); + assert!(TransitionRule::from_tz_string(b"FOO+24", false).is_err()); + assert!(TransitionRule::from_tz_string(b"FOO-24", false).is_err()); + // The maximum valid offset is 23:59:59. + assert!(TransitionRule::from_tz_string(b"FOO23:59:59", false).is_ok()); + } + + #[test] + fn test_all_year_dst() -> Result<(), Error> { + let tz_string = b"EST5EDT,0/0,J365/25"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_v3_file() -> Result<(), Error> { + let bytes = b"TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\x1c\x20\0\0IST\0TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x04\0\0\0\0\x7f\xe8\x17\x80\0\0\0\x1c\x20\0\0IST\0\x01\x01\x0aIST-2IDT,M3.4.4/26,M10.5.0\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![Transition::new(2145916800, 0)], + vec![LocalTimeType::new(7200, false, Some(b"IST"))?], + Vec::new(), + Some(TransitionRule::from(AlternateTime::new( + LocalTimeType::new(7200, false, Some(b"IST"))?, + LocalTimeType::new(10800, true, Some(b"IDT"))?, + RuleDay::month_weekday(3, 4, 4)?, + 93600, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + )?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_rule_day() -> Result<(), Error> { + let rule_day_j1 = RuleDay::julian_1(60)?; + assert_eq!(rule_day_j1.transition_date(2000), (3, 1)); + assert_eq!(rule_day_j1.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j1.unix_time(2000, 43200), 951912000); + + let rule_day_j0 = RuleDay::julian_0(59)?; + assert_eq!(rule_day_j0.transition_date(2000), (2, 29)); + assert_eq!(rule_day_j0.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j0.unix_time(2000, 43200), 951825600); + + let rule_day_mwd = RuleDay::month_weekday(2, 5, 2)?; + assert_eq!(rule_day_mwd.transition_date(2000), (2, 29)); + assert_eq!(rule_day_mwd.transition_date(2001), (2, 27)); + assert_eq!(rule_day_mwd.unix_time(2000, 43200), 951825600); + assert_eq!(rule_day_mwd.unix_time(2001, 43200), 983275200); + + Ok(()) + } + + #[test] + fn test_transition_rule() -> Result<(), Error> { + let transition_rule_fixed = TransitionRule::from(LocalTimeType::new(-36000, false, None)?); + assert_eq!(transition_rule_fixed.find_local_time_type(0)?.offset(), -36000); + + let transition_rule_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )?); + + assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.offset(), 46800); + assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.offset(), 46800); + + let transition_rule_negative_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )?); + + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.offset(), 0); + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.offset(), 0); + + let transition_rule_negative_time_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(0, false, None)?, + LocalTimeType::new(0, true, None)?, + RuleDay::julian_0(100)?, + 0, + RuleDay::julian_0(101)?, + -86500, + )?); + + assert!(transition_rule_negative_time_1.find_local_time_type(8639899)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639900)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639999)?.is_dst()); + assert!(transition_rule_negative_time_1.find_local_time_type(8640000)?.is_dst()); + + let transition_rule_negative_time_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )?); + + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032399)?.offset(), + -10800 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032400)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781199)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781200)?.offset(), + -10800 + ); + + let transition_rule_all_year_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )?); + + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.offset(), -14400); + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.offset(), -14400); + + Ok(()) + } + + #[test] + fn test_transition_rule_overflow() -> Result<(), Error> { + let transition_rule_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-1, false, None)?, + LocalTimeType::new(-1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let transition_rule_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(1, false, None)?, + LocalTimeType::new(1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let min_unix_time = -67768100567971200; + let max_unix_time = 67767976233532799; + + assert!(matches!( + transition_rule_1.find_local_time_type(min_unix_time), + Err(Error::OutOfRange(_)) + )); + assert!(matches!( + transition_rule_2.find_local_time_type(max_unix_time), + Err(Error::OutOfRange(_)) + )); + + Ok(()) + } +} diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs new file mode 100644 index 0000000000..9dfe2d508a --- /dev/null +++ b/src/offset/local/tz_info/timezone.rs @@ -0,0 +1,989 @@ +//! Types related to a time zone. + +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::{cmp::Ordering, fmt, str}; + +use super::rule::{AlternateTime, TransitionRule}; +use super::{DAYS_PER_WEEK, Error, SECONDS_PER_DAY, parser}; +use crate::NaiveDateTime; + +/// Time zone +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct TimeZone { + /// List of transitions + transitions: Vec, + /// List of local time types (cannot be empty) + local_time_types: Vec, + /// List of leap seconds + leap_seconds: Vec, + /// Extra transition rule applicable after the last transition + extra_rule: Option, +} + +impl TimeZone { + /// Returns local time zone. + /// + /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. + pub(crate) fn local(env_tz: Option<&str>) -> Result { + match env_tz { + Some(tz) => Self::from_posix_tz(tz), + None => Self::from_posix_tz("localtime"), + } + } + + /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + fn from_posix_tz(tz_string: &str) -> Result { + // It is commonly agreed (but not standard) that setting an empty `TZ=` uses UTC. + if tz_string.is_empty() { + return Ok(Self::utc()); + } + + if tz_string == "localtime" { + return Self::from_tz_data(&fs::read("/etc/localtime")?); + } + + // attributes are not allowed on if blocks in Rust 1.38 + #[cfg(any(target_os = "android", target_env = "ohos"))] + { + if let Ok(Some(bytes)) = crate::offset::local::tz_data::for_zone(tz_string) { + return Self::from_tz_data(&bytes); + } + } + + let mut chars = tz_string.chars(); + if chars.next() == Some(':') { + return Self::from_file(&mut find_tz_file(chars.as_str())?); + } + + if let Ok(mut file) = find_tz_file(tz_string) { + return Self::from_file(&mut file); + } + + // TZ string extensions are not allowed + let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace()); + let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?; + Self::new( + vec![], + match rule { + TransitionRule::Fixed(local_time_type) => vec![local_time_type], + TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst], + }, + vec![], + Some(rule), + ) + } + + /// Construct a time zone + pub(super) fn new( + transitions: Vec, + local_time_types: Vec, + leap_seconds: Vec, + extra_rule: Option, + ) -> Result { + let new = Self { transitions, local_time_types, leap_seconds, extra_rule }; + new.as_ref().validate()?; + Ok(new) + } + + /// Construct a time zone from the contents of a time zone file + fn from_file(file: &mut File) -> Result { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + Self::from_tz_data(&bytes) + } + + /// Construct a time zone from the contents of a time zone file + /// + /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). + pub(crate) fn from_tz_data(bytes: &[u8]) -> Result { + parser::parse(bytes) + } + + /// Construct a time zone with the specified UTC offset in seconds + fn fixed(ut_offset: i32) -> Result { + Ok(Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::with_offset(ut_offset)?], + leap_seconds: Vec::new(), + extra_rule: None, + }) + } + + /// Construct the time zone associated to UTC + pub(crate) fn utc() -> Self { + Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::UTC], + leap_seconds: Vec::new(), + extra_rule: None, + } + } + + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + self.as_ref().find_local_time_type(unix_time) + } + + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + self.as_ref().find_local_time_type_from_local(local_time) + } + + /// Returns a reference to the time zone + fn as_ref(&self) -> TimeZoneRef<'_> { + TimeZoneRef { + transitions: &self.transitions, + local_time_types: &self.local_time_types, + leap_seconds: &self.leap_seconds, + extra_rule: &self.extra_rule, + } + } +} + +/// Reference to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct TimeZoneRef<'a> { + /// List of transitions + transitions: &'a [Transition], + /// List of local time types (cannot be empty) + local_time_types: &'a [LocalTimeType], + /// List of leap seconds + leap_seconds: &'a [LeapSecond], + /// Extra transition rule applicable after the last transition + extra_rule: &'a Option, +} + +impl<'a> TimeZoneRef<'a> { + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> { + let extra_rule = match self.transitions.last() { + None => match self.extra_rule { + Some(extra_rule) => extra_rule, + None => return Ok(&self.local_time_types[0]), + }, + Some(last_transition) => { + let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) { + Ok(unix_leap_time) => unix_leap_time, + Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + Err(err) => return Err(err), + }; + + if unix_leap_time >= last_transition.unix_leap_time { + match self.extra_rule { + Some(extra_rule) => extra_rule, + None => { + // RFC 8536 3.2: + // "Local time for timestamps on or after the last transition is + // specified by the TZ string in the footer (Section 3.3) if present + // and nonempty; otherwise, it is unspecified." + // + // Older versions of macOS (1.12 and before?) have TZif file with a + // missing TZ string, and use the offset given by the last transition. + return Ok( + &self.local_time_types[last_transition.local_time_type_index] + ); + } + } + } else { + let index = match self + .transitions + .binary_search_by_key(&unix_leap_time, Transition::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let local_time_type_index = if index > 0 { + self.transitions[index - 1].local_time_type_index + } else { + 0 + }; + return Ok(&self.local_time_types[local_time_type_index]); + } + } + }; + + match extra_rule.find_local_time_type(unix_time) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } + + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + // #TODO: this is wrong as we need 'local_time_to_local_leap_time ? + // but ... does the local time even include leap seconds ?? + // let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) { + // Ok(unix_leap_time) => unix_leap_time, + // Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + // Err(err) => return Err(err), + // }; + let local_leap_time = local_time.and_utc().timestamp(); + + // if we have at least one transition, + // we must check _all_ of them, in case of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions + let offset_after_last = if !self.transitions.is_empty() { + let mut prev = self.local_time_types[0]; + + for transition in self.transitions { + let after_ltt = self.local_time_types[transition.local_time_type_index]; + + // the end and start here refers to where the time starts prior to the transition + // and where it ends up after. not the temporal relationship. + let transition_end = + transition.unix_leap_time.saturating_add(i64::from(after_ltt.ut_offset)); + let transition_start = + transition.unix_leap_time.saturating_add(i64::from(prev.ut_offset)); + + match transition_start.cmp(&transition_end) { + Ordering::Greater => { + // backwards transition, eg from DST to regular + // this means a given local time could have one of two possible offsets + if local_leap_time < transition_end { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time >= transition_end + && local_leap_time <= transition_start + { + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); + } else { + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); + } + } + } + Ordering::Equal => { + // should this ever happen? presumably we have to handle it anyway. + if local_leap_time < transition_start { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time == transition_end { + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); + } else { + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); + } + } + } + Ordering::Less => { + // forwards transition, eg from regular to DST + // this means that times that are skipped are invalid local times + if local_leap_time <= transition_start { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time < transition_end { + return Ok(crate::MappedLocalTime::None); + } else if local_leap_time == transition_end { + return Ok(crate::MappedLocalTime::Single(after_ltt)); + } + } + } + + // try the next transition, we are fully after this one + prev = after_ltt; + } + + prev + } else { + self.local_time_types[0] + }; + + if let Some(extra_rule) = self.extra_rule { + match extra_rule.find_local_time_type_from_local(local_time) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } else { + Ok(crate::MappedLocalTime::Single(offset_after_last)) + } + } + + /// Check time zone inputs + fn validate(&self) -> Result<(), Error> { + // Check local time types + let local_time_types_size = self.local_time_types.len(); + if local_time_types_size == 0 { + return Err(Error::TimeZone("list of local time types must not be empty")); + } + + // Check transitions + let mut i_transition = 0; + while i_transition < self.transitions.len() { + if self.transitions[i_transition].local_time_type_index >= local_time_types_size { + return Err(Error::TimeZone("invalid local time type index")); + } + + if i_transition + 1 < self.transitions.len() + && self.transitions[i_transition].unix_leap_time + >= self.transitions[i_transition + 1].unix_leap_time + { + return Err(Error::TimeZone("invalid transition")); + } + + i_transition += 1; + } + + // Check leap seconds + if !(self.leap_seconds.is_empty() + || self.leap_seconds[0].unix_leap_time >= 0 + && self.leap_seconds[0].correction.saturating_abs() == 1) + { + return Err(Error::TimeZone("invalid leap second")); + } + + let min_interval = SECONDS_PER_28_DAYS - 1; + + let mut i_leap_second = 0; + while i_leap_second < self.leap_seconds.len() { + if i_leap_second + 1 < self.leap_seconds.len() { + let x0 = &self.leap_seconds[i_leap_second]; + let x1 = &self.leap_seconds[i_leap_second + 1]; + + let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time); + let abs_diff_correction = + x1.correction.saturating_sub(x0.correction).saturating_abs(); + + if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) { + return Err(Error::TimeZone("invalid leap second")); + } + } + i_leap_second += 1; + } + + // Check extra rule + let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) { + (Some(rule), Some(trans)) => (rule, trans), + _ => return Ok(()), + }; + + let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index]; + let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) { + Ok(unix_time) => unix_time, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) { + Ok(rule_local_time_type) => rule_local_time_type, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset + && last_local_time_type.is_dst == rule_local_time_type.is_dst + && match (&last_local_time_type.name, &rule_local_time_type.name) { + (Some(x), Some(y)) => x.equal(y), + (None, None) => true, + _ => false, + }; + + if !check { + return Err(Error::TimeZone( + "extra transition rule is inconsistent with the last transition", + )); + } + + Ok(()) + } + + /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone + const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { + let mut unix_leap_time = unix_time; + + let mut i = 0; + while i < self.leap_seconds.len() { + let leap_second = &self.leap_seconds[i]; + + if unix_leap_time < leap_second.unix_leap_time { + break; + } + + unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) { + Some(unix_leap_time) => unix_leap_time, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + i += 1; + } + + Ok(unix_leap_time) + } + + /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone + fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { + if unix_leap_time == i64::MIN { + return Err(Error::OutOfRange("out of range operation")); + } + + let index = match self + .leap_seconds + .binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 }; + + match unix_leap_time.checked_sub(correction as i64) { + Some(unix_time) => Ok(unix_time), + None => Err(Error::OutOfRange("out of range operation")), + } + } + + /// The UTC time zone + const UTC: TimeZoneRef<'static> = TimeZoneRef { + transitions: &[], + local_time_types: &[LocalTimeType::UTC], + leap_seconds: &[], + extra_rule: &None, + }; +} + +/// Transition of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct Transition { + /// Unix leap time + unix_leap_time: i64, + /// Index specifying the local time type of the transition + local_time_type_index: usize, +} + +impl Transition { + /// Construct a TZif file transition + pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { + Self { unix_leap_time, local_time_type_index } + } + + /// Returns Unix leap time + const fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// Leap second of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct LeapSecond { + /// Unix leap time + unix_leap_time: i64, + /// Leap second correction + correction: i32, +} + +impl LeapSecond { + /// Construct a TZif file leap second + pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self { + Self { unix_leap_time, correction } + } + + /// Returns Unix leap time + const fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// ASCII-encoded fixed-capacity string, used for storing time zone names +#[derive(Copy, Clone, Eq, PartialEq)] +struct TimeZoneName { + /// Length-prefixed string buffer + bytes: [u8; 32], +} + +impl TimeZoneName { + /// Construct a time zone name + /// + /// man tzfile(5): + /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII + /// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with + /// POSIX requirements for time zone abbreviations. + /// + /// That is a *should*, not a *must*: `zic` and glibc accept longer + /// designations and a slightly wider character set (notably `_`). Rejecting + /// an over-long designation would fail the whole TZif parse and make `Local` + /// fall back to UTC, so we keep the offset and simply truncate the name to + /// the 31-byte buffer, and additionally allow `_`. + fn new(input: &[u8]) -> Result { + // Truncate over-long designations rather than failing the parse. + let len = input.len().min(31); + + if len < 3 { + return Err(Error::LocalTimeType("time zone name must have at least 3 characters")); + } + + let mut bytes = [0; 32]; + bytes[0] = len as u8; + + let mut i = 0; + while i < len { + let b = input[i]; + match b { + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' | b'_' => {} + _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), + } + + bytes[i + 1] = b; + i += 1; + } + + Ok(Self { bytes }) + } + + /// Returns time zone name as a byte slice + fn as_bytes(&self) -> &[u8] { + let len = self.bytes[0] as usize; + &self.bytes[1..=len] + } + + /// Check if two time zone names are equal + fn equal(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl AsRef for TimeZoneName { + fn as_ref(&self) -> &str { + // SAFETY: ASCII is valid UTF-8 + unsafe { str::from_utf8_unchecked(self.as_bytes()) } + } +} + +impl fmt::Debug for TimeZoneName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +/// Local time type associated to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct LocalTimeType { + /// Offset from UTC in seconds + pub(super) ut_offset: i32, + /// Daylight Saving Time indicator + is_dst: bool, + /// Time zone name + name: Option, +} + +impl LocalTimeType { + /// Construct a local time type + pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result { + if ut_offset == i32::MIN { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + let name = match name { + Some(name) => TimeZoneName::new(name)?, + None => return Ok(Self { ut_offset, is_dst, name: None }), + }; + + Ok(Self { ut_offset, is_dst, name: Some(name) }) + } + + /// Construct a local time type with the specified UTC offset in seconds + pub(super) const fn with_offset(ut_offset: i32) -> Result { + if ut_offset == i32::MIN { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + Ok(Self { ut_offset, is_dst: false, name: None }) + } + + /// Returns offset from UTC in seconds + pub(crate) const fn offset(&self) -> i32 { + self.ut_offset + } + + /// Returns daylight saving time indicator + pub(super) const fn is_dst(&self) -> bool { + self.is_dst + } + + pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None }; +} + +/// Open the TZif file corresponding to a TZ string +fn find_tz_file(path: impl AsRef) -> Result { + // Don't check system timezone directories on non-UNIX platforms + #[cfg(not(unix))] + return Ok(File::open(path)?); + + #[cfg(unix)] + { + let path = path.as_ref(); + if path.is_absolute() { + return Ok(File::open(path)?); + } + + for folder in &ZONE_INFO_DIRECTORIES { + if let Ok(file) = File::open(PathBuf::from(folder).join(path)) { + return Ok(file); + } + } + + Err(Error::Io(io::ErrorKind::NotFound.into())) + } +} + +// Possible system timezone directories +#[cfg(unix)] +const ZONE_INFO_DIRECTORIES: [&str; 4] = + ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"]; + +/// Number of seconds in one week +pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK; +/// Number of seconds in 28 days +const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; + +#[cfg(test)] +mod tests { + use super::super::Error; + use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; + + #[test] + fn test_no_dst() -> Result<(), Error> { + let tz_string = b"HST10"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into()); + Ok(()) + } + + #[test] + fn test_error() -> Result<(), Error> { + assert!(matches!( + TransitionRule::from_tz_string(b"IST-1GMT0", false), + Err(Error::UnsupportedTzString(_)) + )); + assert!(matches!( + TransitionRule::from_tz_string(b"EET-2EEST", false), + Err(Error::UnsupportedTzString(_)) + )); + + Ok(()) + } + + #[test] + fn test_v1_file_with_leap_seconds() -> Result<(), Error> { + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_v2_file() -> Result<(), Error> { + let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![ + Transition::new(-2334101314, 1), + Transition::new(-1157283000, 2), + Transition::new(-1155436200, 1), + Transition::new(-880198200, 3), + Transition::new(-769395600, 4), + Transition::new(-765376200, 1), + Transition::new(-712150200, 5), + ], + vec![ + LocalTimeType::new(-37886, false, Some(b"LMT"))?, + LocalTimeType::new(-37800, false, Some(b"HST"))?, + LocalTimeType::new(-34200, true, Some(b"HDT"))?, + LocalTimeType::new(-34200, true, Some(b"HWT"))?, + LocalTimeType::new(-34200, true, Some(b"HPT"))?, + LocalTimeType::new(-36000, false, Some(b"HST"))?, + ], + Vec::new(), + Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1156939200)?, + LocalTimeType::new(-34200, true, Some(b"HDT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(1546300800)?, + LocalTimeType::new(-36000, false, Some(b"HST"))? + ); + + Ok(()) + } + + #[test] + fn test_no_tz_string() -> Result<(), Error> { + // Guayaquil from macOS 10.11 + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x01\0\0\0\x02\0\0\0\x08\xb6\xa4B\x18\x01\xff\xff\xb6h\0\0\xff\xff\xb9\xb0\0\x04QMT\0ECT\0\0\0\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + dbg!(&time_zone); + + let time_zone_result = TimeZone::new( + vec![Transition::new(-1230749160, 1)], + vec![ + LocalTimeType::new(-18840, false, Some(b"QMT"))?, + LocalTimeType::new(-18000, false, Some(b"ECT"))?, + ], + Vec::new(), + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1500000000)?, + LocalTimeType::new(-18840, false, Some(b"QMT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(0)?, + LocalTimeType::new(-18000, false, Some(b"ECT"))? + ); + + Ok(()) + } + + #[test] + fn test_tz_ascii_str() -> Result<(), Error> { + assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET"); + assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT"); + assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg"); + assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02"); + assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); + assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) + assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); + // Designations longer than the historical 6/7-char limit are accepted: + // `zic`/glibc allow them, and rejecting one fails the whole TZif file. + assert_eq!(TimeZoneName::new(b"12345678")?.as_bytes(), b"12345678"); + // Custom designations containing `_` parse too (regression: a name like + // this previously made the entire zone fail to load, so `Local` fell + // back to UTC instead of the file's real offset). + assert_eq!(TimeZoneName::new(b"Solar_Custom_Zone")?.as_bytes(), b"Solar_Custom_Zone"); + // Over-long designations are truncated to fit the buffer (keeping the + // offset) rather than failing the parse. + assert_eq!(TimeZoneName::new(&[b'A'; 64])?.as_bytes(), &[b'A'; 31]); + assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_)))); + + Ok(()) + } + + #[test] + fn test_time_zone() -> Result<(), Error> { + let utc = LocalTimeType::UTC; + let cet = LocalTimeType::with_offset(3600)?; + + let utc_local_time_types = vec![utc]; + let fixed_extra_rule = TransitionRule::from(cet); + + let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?; + let time_zone_2 = + TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?; + let time_zone_3 = + TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?; + let time_zone_4 = TimeZone::new( + vec![Transition::new(i32::MIN.into(), 0), Transition::new(0, 1)], + vec![utc, cet], + Vec::new(), + Some(fixed_extra_rule), + )?; + + assert_eq!(*time_zone_1.find_local_time_type(0)?, utc); + assert_eq!(*time_zone_2.find_local_time_type(0)?, cet); + + assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc); + assert_eq!(*time_zone_3.find_local_time_type(0)?, utc); + + assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc); + assert_eq!(*time_zone_4.find_local_time_type(0)?, cet); + + let time_zone_err = TimeZone::new( + vec![Transition::new(0, 0)], + utc_local_time_types, + vec![], + Some(fixed_extra_rule), + ); + assert!(time_zone_err.is_err()); + + Ok(()) + } + + #[test] + fn test_time_zone_from_posix_tz() -> Result<(), Error> { + #[cfg(unix)] + { + // if the TZ var is set, this essentially _overrides_ the + // time set by the localtime symlink + // so just ensure that ::local() acts as expected + // in this case + if let Ok(tz) = std::env::var("TZ") { + let time_zone_local = TimeZone::local(Some(tz.as_str()))?; + let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; + assert_eq!(time_zone_local, time_zone_local_1); + } + + // `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have + // a time zone database, like for example some docker containers. + // In that case skip the test. + if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") { + assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); + } + } + + assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err()); + assert_eq!(TimeZone::from_posix_tz("").unwrap().find_local_time_type(0)?.offset(), 0); + + Ok(()) + } + + #[test] + fn test_leap_seconds() -> Result<(), Error> { + let time_zone = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + let time_zone_ref = time_zone.as_ref(); + + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601))); + + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624))); + + Ok(()) + } + + #[test] + fn test_leap_seconds_overflow() -> Result<(), Error> { + let time_zone_err = TimeZone::new( + vec![Transition::new(i64::MIN, 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + Some(TransitionRule::from(LocalTimeType::UTC)), + ); + assert!(time_zone_err.is_err()); + + let time_zone = TimeZone::new( + vec![Transition::new(i64::MAX, 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + None, + )?; + assert!(matches!( + time_zone.find_local_time_type(i64::MAX), + Err(Error::FindLocalTimeType(_)) + )); + + Ok(()) + } + + #[test] + fn test_find_local_time_type_from_local_overflow() -> Result<(), Error> { + use crate::MappedLocalTime; + + let local = + crate::NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + // A transition timestamp near `i64::MAX` plus a positive offset overflows when mapping a + // local time; near `i64::MIN` a negative offset underflows. + let near_max = TimeZone::new( + vec![Transition::new(i64::MAX, 1)], + vec![LocalTimeType::UTC, LocalTimeType::new(3600, false, Some(b"FOO"))?], + Vec::new(), + None, + )?; + assert_eq!( + near_max.find_local_time_type_from_local(local)?, + MappedLocalTime::Single(LocalTimeType::UTC) + ); + + let near_min = TimeZone::new( + vec![Transition::new(i64::MIN, 0)], + vec![LocalTimeType::new(-3600, false, Some(b"FOO"))?, LocalTimeType::UTC], + Vec::new(), + None, + )?; + assert!(near_min.find_local_time_type_from_local(local).is_ok()); + + Ok(()) + } +} diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs new file mode 100644 index 0000000000..890d550456 --- /dev/null +++ b/src/offset/local/unix.rs @@ -0,0 +1,171 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; + +use super::tz_info::TimeZone; +use super::{FixedOffset, NaiveDateTime}; +use crate::MappedLocalTime; + +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { + offset(local, true) +} + +fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime { + TZ_INFO.with(|maybe_cache| { + maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) + }) +} + +// we have to store the `Cache` in an option as it can't +// be initialized in a static context. +thread_local! { + static TZ_INFO: RefCell> = Default::default(); +} + +enum Source { + LocalTime { mtime: SystemTime }, + Environment { hash: u64 }, +} + +impl Source { + fn new(env_tz: Option<&str>) -> Source { + match env_tz { + Some(tz) => { + let mut hasher = hash_map::DefaultHasher::new(); + hasher.write(tz.as_bytes()); + let hash = hasher.finish(); + Source::Environment { hash } + } + None => match fs::symlink_metadata("/etc/localtime") { + Ok(data) => Source::LocalTime { + // we have to pick a sensible default when the mtime fails + // by picking SystemTime::now() we raise the probability of + // the cache being invalidated if/when the mtime starts working + mtime: data.modified().unwrap_or_else(|_| SystemTime::now()), + }, + Err(_) => { + // as above, now() should be a better default than some constant + // TODO: see if we can improve caching in the case where the fallback is a valid timezone + Source::LocalTime { mtime: SystemTime::now() } + } + }, + } + } +} + +struct Cache { + zone: TimeZone, + source: Source, + last_checked: SystemTime, +} + +#[cfg(target_os = "aix")] +const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo"; + +#[cfg(not(any(target_os = "android", target_os = "aix", target_env = "ohos")))] +const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; + +fn fallback_timezone() -> Option { + let tz_name = iana_time_zone::get_timezone().ok()?; + #[cfg(not(any(target_os = "android", target_env = "ohos")))] + let bytes = fs::read(format!("{TZDB_LOCATION}/{tz_name}")).ok()?; + #[cfg(any(target_os = "android", target_env = "ohos"))] + let bytes = crate::offset::local::tz_data::for_zone(&tz_name).ok()??; + TimeZone::from_tz_data(&bytes).ok() +} + +impl Default for Cache { + fn default() -> Cache { + // default to UTC if no local timezone can be found + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_deref(); + Cache { + last_checked: SystemTime::now(), + source: Source::new(env_ref), + zone: current_zone(env_ref), + } + } +} + +fn current_zone(var: Option<&str>) -> TimeZone { + TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc) +} + +impl Cache { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime { + let now = SystemTime::now(); + + match now.duration_since(self.last_checked) { + // If the cache has been around for less than a second then we reuse it + // unconditionally. This is a reasonable tradeoff because the timezone + // generally won't be changing _that_ often, but if the time zone does + // change, it will reflect sufficiently quickly from an application + // user's perspective. + Ok(d) if d.as_secs() < 1 => (), + Ok(_) | Err(_) => { + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_deref(); + let new_source = Source::new(env_ref); + + let out_of_date = match (&self.source, &new_source) { + // change from env to file or file to env, must recreate the zone + (Source::Environment { .. }, Source::LocalTime { .. }) + | (Source::LocalTime { .. }, Source::Environment { .. }) => true, + // stay as file, but mtime has changed + (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime }) + if old_mtime != mtime => + { + true + } + // stay as env, but hash of variable has changed + (Source::Environment { hash: old_hash }, Source::Environment { hash }) + if old_hash != hash => + { + true + } + // cache can be reused + _ => false, + }; + + if out_of_date { + self.zone = current_zone(env_ref); + } + + self.last_checked = now; + self.source = new_source; + } + } + + if !local { + let offset = self + .zone + .find_local_time_type(d.and_utc().timestamp()) + .expect("unable to select local time type") + .offset(); + + return match FixedOffset::east_opt(offset) { + Some(offset) => MappedLocalTime::Single(offset), + None => MappedLocalTime::None, + }; + } + + // we pass through the year as the year of a local point in time must either be valid in that locale, or + // the entire time was skipped in which case we will return MappedLocalTime::None anyway. + self.zone + .find_local_time_type_from_local(d) + .expect("unable to select local time type") + .and_then(|o| FixedOffset::east_opt(o.offset())) + } +} diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs new file mode 100644 index 0000000000..cb563b4ebb --- /dev/null +++ b/src/offset/local/win_bindings.rs @@ -0,0 +1,59 @@ +#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] + +windows_link::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); +pub type BOOL = i32; +#[repr(C)] +#[derive(Clone, Copy)] +pub struct DYNAMIC_TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, + pub TimeZoneKeyName: [u16; 128], + pub DynamicDaylightTimeDisabled: bool, +} +impl Default for DYNAMIC_TIME_ZONE_INFORMATION { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FILETIME { + pub dwLowDateTime: u32, + pub dwHighDateTime: u32, +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct SYSTEMTIME { + pub wYear: u16, + pub wMonth: u16, + pub wDayOfWeek: u16, + pub wDay: u16, + pub wHour: u16, + pub wMinute: u16, + pub wSecond: u16, + pub wMilliseconds: u16, +} +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, +} +impl Default for TIME_ZONE_INFORMATION { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs new file mode 100644 index 0000000000..dcab6c44e6 --- /dev/null +++ b/src/offset/local/windows.rs @@ -0,0 +1,293 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::cmp::Ordering; +use std::mem::MaybeUninit; +use std::ptr; + +use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; + +use crate::offset::local::{Transition, lookup_with_dst_transitions}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; + +// We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates +// as Chrono. Also it really isn't that difficult to work out the correct offset from the provided +// DST rules. +// +// This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC +// falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is +// very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`. +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + // Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be + // using the rules for the year of the corresponding local time. But this matches what + // `SystemTimeToTzSpecificLocalTime` is documented to do. + let tz_info = match TzInfo::for_year(utc.year()) { + Some(tz_info) => tz_info, + None => return MappedLocalTime::None, + }; + let offset = match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + if dst_transition_utc < std_transition_utc { + match utc >= &dst_transition_utc && utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } else { + match utc >= &std_transition_utc && utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + } + (Some(std_transition), None) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + match utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } + (None, Some(dst_transition)) => { + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + match utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + (None, None) => tz_info.std_offset, + }; + MappedLocalTime::Single(offset) +} + +// We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle +// ambiguous cases (during a DST transition). Instead we get the timezone information for the +// current year and compute it ourselves, like we do on Unix. +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { + let tz_info = match TzInfo::for_year(local.year()) { + Some(tz_info) => tz_info, + None => return MappedLocalTime::None, + }; + // Create a sorted slice of transitions and use `lookup_with_dst_transitions`. + match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition = + Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset); + let dst_transition = + Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset); + let transitions = match std_transition.cmp(&dst_transition) { + Ordering::Less => [std_transition, dst_transition], + Ordering::Greater => [dst_transition, std_transition], + Ordering::Equal => { + // This doesn't make sense. Let's just return the standard offset. + return MappedLocalTime::Single(tz_info.std_offset); + } + }; + lookup_with_dst_transitions(&transitions, *local) + } + (Some(std_transition), None) => { + let transitions = + [Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, Some(dst_transition)) => { + let transitions = + [Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, None) => MappedLocalTime::Single(tz_info.std_offset), + } +} + +// The basis for Windows timezone and DST support has been in place since Windows 2000. It does not +// allow for complex rules like the IANA timezone database: +// - A timezone has the same base offset the whole year. +// - There seem to be either zero or two DST transitions (but we support having just one). +// - As of Vista(?) only years from 2004 until a few years into the future are supported. +// - All other years get the base settings, which seem to be that of the current year. +// +// These details don't matter much, we just work with the offsets and transition dates Windows +// returns through `GetTimeZoneInformationForYear` for a particular year. +struct TzInfo { + // Offset from UTC during standard time. + std_offset: FixedOffset, + // Offset from UTC during daylight saving time. + dst_offset: FixedOffset, + // Transition from standard time to daylight saving time, given in local standard time. + std_transition: Option, + // Transition from daylight saving time to standard time, given in local daylight saving time. + dst_transition: Option, +} + +impl TzInfo { + fn for_year(year: i32) -> Option { + // The API limits years to 1601..=30827. + // Working with timezones and daylight saving time this far into the past or future makes + // little sense. But whatever is extrapolated for 1601 or 30827 is what can be extrapolated + // for years beyond. + let ref_year = year.clamp(1601, 30827) as u16; + let tz_info = unsafe { + let mut tz_info = MaybeUninit::::uninit(); + if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 { + return None; + } + tz_info.assume_init() + }; + let std_offset = (tz_info.Bias) + .checked_add(tz_info.StandardBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; + let dst_offset = (tz_info.Bias) + .checked_add(tz_info.DaylightBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; + Some(TzInfo { + std_offset, + dst_offset, + std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?, + dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?, + }) + } +} + +/// Resolve a `SYSTEMTIME` object to an `Option`. +/// +/// A `SYSTEMTIME` within a `TIME_ZONE_INFORMATION` struct can be zero to indicate there is no +/// transition. +/// If it has year, month and day values it is a concrete date. +/// If the year is missing the `SYSTEMTIME` is a rule, which this method resolves for the provided +/// year. A rule has a month, weekday, and nth weekday of the month as components. +/// +/// Returns `Err` if any of the values is invalid, which should never happen. +fn naive_date_time_from_system_time( + st: SYSTEMTIME, + year: i32, +) -> Result, ()> { + if st.wYear == 0 && st.wMonth == 0 { + return Ok(None); + } + let time = NaiveTime::from_hms_milli_opt( + st.wHour as u32, + st.wMinute as u32, + st.wSecond as u32, + st.wMilliseconds as u32, + ) + .ok_or(())?; + + if st.wYear != 0 { + // We have a concrete date. + let date = + NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).ok_or(())?; + return Ok(Some(date.and_time(time))); + } + + // Resolve a rule with month, weekday, and nth weekday of the month to a date in the current + // year. + let weekday = match st.wDayOfWeek { + 0 => Weekday::Sun, + 1 => Weekday::Mon, + 2 => Weekday::Tue, + 3 => Weekday::Wed, + 4 => Weekday::Thu, + 5 => Weekday::Fri, + 6 => Weekday::Sat, + _ => return Err(()), + }; + let nth_day = match st.wDay { + 1..=5 => st.wDay as u8, + _ => return Err(()), + }; + let date = NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, nth_day) + .or_else(|| NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, 4)) + .ok_or(())?; // `st.wMonth` must be invalid + Ok(Some(date.and_time(time))) +} + +#[cfg(test)] +mod tests { + use crate::offset::local::win_bindings::{ + FILETIME, SYSTEMTIME, SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, + }; + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta}; + use crate::{Datelike, TimeZone, Timelike}; + use std::mem::MaybeUninit; + use std::ptr; + + #[test] + fn verify_against_tz_specific_local_time_to_system_time() { + // The implementation in Windows itself is the source of truth on how to work with the OS + // timezone information. This test compares for every hour over a period of 125 years our + // implementation to `TzSpecificLocalTimeToSystemTime`. + // + // This uses parts of a previous Windows `Local` implementation in chrono. + fn from_local_time(dt: &NaiveDateTime) -> DateTime { + let st = system_time_from_naive_date_time(dt); + let utc_time = local_to_utc_time(&st); + let utc_secs = system_time_as_unix_seconds(&utc_time); + let local_secs = system_time_as_unix_seconds(&st); + let offset = (local_secs - utc_secs) as i32; + let offset = FixedOffset::east_opt(offset).unwrap(); + DateTime::from_naive_utc_and_offset(*dt - offset, offset) + } + fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { + SYSTEMTIME { + // Valid values: 1601-30827 + wYear: dt.year() as u16, + // Valid values:1-12 + wMonth: dt.month() as u16, + // Valid values: 0-6, starting Sunday. + // NOTE: enum returns 1-7, starting Monday, so we are + // off here, but this is not currently used in local. + wDayOfWeek: dt.weekday() as u16, + // Valid values: 1-31 + wDay: dt.day() as u16, + // Valid values: 0-23 + wHour: dt.hour() as u16, + // Valid values: 0-59 + wMinute: dt.minute() as u16, + // Valid values: 0-59 + wSecond: dt.second() as u16, + // Valid values: 0-999 + wMilliseconds: 0, + } + } + fn local_to_utc_time(local: &SYSTEMTIME) -> SYSTEMTIME { + let mut sys_time = MaybeUninit::::uninit(); + unsafe { TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()) }; + // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can + // assume the value is initialized. + unsafe { sys_time.assume_init() } + } + const HECTONANOSECS_IN_SEC: i64 = 10_000_000; + const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; + fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> i64 { + let mut init = MaybeUninit::::uninit(); + unsafe { + SystemTimeToFileTime(st, init.as_mut_ptr()); + } + // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is + // initialized. + let filetime = unsafe { init.assume_init() }; + let bit_shift = + ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); + (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC + } + + let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 30, 0).unwrap(); + + while date.year() < 2078 { + // Windows doesn't handle non-existing dates, it just treats it as valid. + if let Some(our_result) = Local.from_local_datetime(&date).earliest() { + assert_eq!(from_local_time(&date), our_result); + } + date += TimeDelta::try_hours(1).unwrap(); + } + } +} diff --git a/src/offset/mod.rs b/src/offset/mod.rs index f317a72289..295700b4f7 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -20,71 +20,164 @@ use core::fmt; -use format::{parse, ParseResult, Parsed, StrftimeItems}; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use Weekday; -use {Date, DateTime}; +use crate::Weekday; +use crate::format::{ParseResult, Parsed, StrftimeItems, parse}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +#[allow(deprecated)] +use crate::{Date, DateTime}; -/// The conversion result from the local time to the timezone-aware datetime types. +pub(crate) mod fixed; +pub use self::fixed::FixedOffset; + +#[cfg(feature = "clock")] +pub(crate) mod local; +#[cfg(feature = "clock")] +pub use self::local::Local; + +pub(crate) mod utc; +pub use self::utc::Utc; + +/// The result of mapping a local time to a concrete instant in a given time zone. +/// +/// The calculation to go from a local time (wall clock time) to an instant in UTC can end up in +/// three cases: +/// * A single, simple result. +/// * An ambiguous result when the clock is turned backwards during a transition due to for example +/// DST. +/// * No result when the clock is turned forwards during a transition due to for example DST. +/// +///
+/// +/// In wasm, when using [`Local`], only the [`LocalResult::Single`] variant is returned. +/// Specifically: +/// +/// * When the clock is turned backwards, where `Ambiguous(earliest, latest)` would be expected, +/// `Single(earliest)` is returned instead. +/// * When the clock is turned forwards, where `None` would be expected, `Single(t)` is returned, +/// with `t` being the requested local time represented as though there is no transition on that +/// day (i.e. still "summer time") +/// +/// This is caused because of limitations in the JavaScript +/// [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +/// API, which always parses a local time as a single, valid time - even for an +/// input which describes a nonexistent or ambiguous time. +/// +/// See further discussion and workarounds in . +/// +///
+/// +/// When the clock is turned backwards it creates a _fold_ in local time, during which the local +/// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during +/// which the local time is _missing_, or does not exist. +/// +/// Chrono does not return a default choice or invalid data during time zone transitions, but has +/// the `MappedLocalTime` type to help deal with the result correctly. +/// +/// The type of `T` is usually a [`DateTime`] but may also be only an offset. +pub type MappedLocalTime = LocalResult; #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Old name of [`MappedLocalTime`]. See that type for more documentation. pub enum LocalResult { - /// Given local time representation is invalid. - /// This can occur when, for example, the positive timezone transition. - None, - /// Given local time representation has a single unique result. + /// The local time maps to a single unique result. Single(T), - /// Given local time representation has multiple results and thus ambiguous. - /// This can occur when, for example, the negative timezone transition. - Ambiguous(T /*min*/, T /*max*/), + + /// The local time is _ambiguous_ because there is a _fold_ in the local time. + /// + /// This variant contains the two possible results, in the order `(earliest, latest)`. + Ambiguous(T, T), + + /// The local time does not exist because there is a _gap_ in the local time. + /// + /// This variant may also be returned if there was an error while resolving the local time, + /// caused by for example missing time zone data files, an error in an OS API, or overflow. + None, } -impl LocalResult { - /// Returns `Some` only when the conversion result is unique, or `None` otherwise. +impl MappedLocalTime { + /// Returns `Some` if the time zone mapping has a single result. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _fold_ or _gap_ in the local time, or if there was + /// an error. + #[must_use] pub fn single(self) -> Option { match self { - LocalResult::Single(t) => Some(t), + MappedLocalTime::Single(t) => Some(t), _ => None, } } - /// Returns `Some` for the earliest possible conversion result, or `None` if none. + /// Returns the earliest possible result of the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. + #[must_use] pub fn earliest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t), _ => None, } } - /// Returns `Some` for the latest possible conversion result, or `None` if none. + /// Returns the latest possible result of the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. + #[must_use] pub fn latest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(_, t) => Some(t), _ => None, } } - /// Maps a `LocalResult` into `LocalResult` with given function. - pub fn map U>(self, mut f: F) -> LocalResult { + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. + #[must_use] + pub fn map U>(self, mut f: F) -> MappedLocalTime { + match self { + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)), + MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)), + } + } + + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. + /// + /// Returns `MappedLocalTime::None` if the function returns `None`. + #[must_use] + pub(crate) fn and_then Option>(self, mut f: F) -> MappedLocalTime { match self { - LocalResult::None => LocalResult::None, - LocalResult::Single(v) => LocalResult::Single(f(v)), - LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => match f(v) { + Some(new) => MappedLocalTime::Single(new), + None => MappedLocalTime::None, + }, + MappedLocalTime::Ambiguous(min, max) => match (f(min), f(max)) { + (Some(min), Some(max)) => MappedLocalTime::Ambiguous(min, max), + _ => MappedLocalTime::None, + }, } } } -impl LocalResult> { +#[allow(deprecated)] +impl MappedLocalTime> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] - pub fn and_time(self, time: NaiveTime) -> LocalResult> { + #[must_use] + pub fn and_time(self, time: NaiveTime) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_time(time).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_time(time).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -93,12 +186,13 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] - pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { + #[must_use] + pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_hms_opt(hour, min, sec).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -108,18 +202,19 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_milli_opt( self, hour: u32, min: u32, sec: u32, milli: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_milli_opt(hour, min, sec, milli) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -129,18 +224,19 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_micro_opt( self, hour: u32, min: u32, sec: u32, micro: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_micro_opt(hour, min, sec, micro) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -150,30 +246,42 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_nano_opt( self, hour: u32, min: u32, sec: u32, nano: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_nano_opt(hour, min, sec, nano) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } } -impl LocalResult { - /// Returns the single unique conversion result, or panics accordingly. +impl MappedLocalTime { + /// Returns a single unique conversion result or panics. + /// + /// `unwrap()` is best combined with time zone types where the mapping can never fail like + /// [`Utc`] and [`FixedOffset`]. Note that for [`FixedOffset`] there is a rare case where a + /// resulting [`DateTime`] can be out of range. + /// + /// # Panics + /// + /// Panics if the local time falls within a _fold_ or a _gap_ in the local time, and on any + /// error that may have been returned by the type implementing [`TimeZone`]. + #[must_use] + #[track_caller] pub fn unwrap(self) -> T { match self { - LocalResult::None => panic!("No such local time"), - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, t2) => { - panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) + MappedLocalTime::None => panic!("No such local time"), + MappedLocalTime::Single(t) => t, + MappedLocalTime::Ambiguous(t1, t2) => { + panic!("Ambiguous local time, ranging from {t1:?} to {t2:?}") } } } @@ -187,14 +295,34 @@ pub trait Offset: Sized + Clone + fmt::Debug { /// The time zone. /// -/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and -/// [`DateTime`](../struct.DateTime.html) types. +/// The methods here are the primary constructors for the [`DateTime`] type. pub trait TimeZone: Sized + Clone { /// An associated offset type. /// This type is used to store the actual offset in date and time types. /// The original `TimeZone` value can be recovered via `TimeZone::from_offset`. type Offset: Offset; + /// Make a new `DateTime` from year, month, day, time components and current time zone. + /// + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// Returns `MappedLocalTime::None` on invalid input data. + fn with_ymd_and_hms( + &self, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + ) -> MappedLocalTime> { + match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) + { + Some(dt) => self.from_local_datetime(&dt), + None => MappedLocalTime::None, + } + } + /// Makes a new `Date` from year, month, day and the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// @@ -202,14 +330,8 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.ymd(2015, 5, 15).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] fn ymd(&self, year: i32, month: u32, day: u32) -> Date { self.ymd_opt(year, month, day).unwrap() } @@ -221,19 +343,12 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, LocalResult, TimeZone}; - /// - /// assert_eq!(Utc.ymd_opt(2015, 5, 15).unwrap().to_string(), "2015-05-15UTC"); - /// assert_eq!(Utc.ymd_opt(2000, 0, 0), LocalResult::None); - /// ``` - fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] + fn ymd_opt(&self, year: i32, month: u32, day: u32) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -244,14 +359,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid DOY. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.yo(2015, 135).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn yo(&self, year: i32, ordinal: u32) -> Date { self.yo_opt(year, ordinal).unwrap() } @@ -263,10 +375,15 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid DOY. - fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] + fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -279,14 +396,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, Weekday, TimeZone}; - /// - /// assert_eq!(Utc.isoywd(2015, 20, Weekday::Fri).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date { self.isoywd_opt(year, week, weekday).unwrap() } @@ -300,10 +414,15 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid week number. - fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] + fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> MappedLocalTime> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -311,16 +430,15 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// Panics on the out-of-range number of seconds and/or invalid nanosecond, - /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). - /// - /// # Example + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// - /// ``` - /// use chrono::{Utc, TimeZone}; + /// # Panics /// - /// assert_eq!(Utc.timestamp(1431648000, 0).to_string(), "2015-05-15 00:00:00 UTC"); - /// ``` + /// Panics on the out-of-range number of seconds and/or invalid nanosecond, + /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). + #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime { self.timestamp_opt(secs, nsecs).unwrap() } @@ -329,12 +447,26 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// Returns `LocalResult::None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise always returns `LocalResult::Single`. - fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { - match NaiveDateTime::from_timestamp_opt(secs, nsecs) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), - None => LocalResult::None, + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `MappedLocalTime::None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise always returns `MappedLocalTime::Single`. + /// + /// # Example + /// + /// ``` + /// use chrono::{TimeZone, Utc}; + /// + /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); + /// ``` + fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime> { + match DateTime::from_timestamp(secs, nsecs) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -343,14 +475,7 @@ pub trait TimeZone: Sized + Clone { /// /// Panics on out-of-range number of milliseconds for a non-panicking /// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt). - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.timestamp_millis(1431648000).timestamp(), 1431648); - /// ``` + #[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")] fn timestamp_millis(&self, millis: i64) -> DateTime { self.timestamp_millis_opt(millis).unwrap() } @@ -359,54 +484,63 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// - /// Returns `LocalResult::None` on out-of-range number of milliseconds + /// Returns `MappedLocalTime::None` on out-of-range number of milliseconds /// and/or invalid nanosecond, otherwise always returns - /// `LocalResult::Single`. + /// `MappedLocalTime::Single`. /// /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone, LocalResult}; + /// use chrono::{MappedLocalTime, TimeZone, Utc}; /// match Utc.timestamp_millis_opt(1431648000) { - /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), + /// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), /// }; /// ``` - fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { - let (mut secs, mut millis) = (millis / 1000, millis % 1000); - if millis < 0 { - secs -= 1; - millis += 1000; + fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime> { + match DateTime::from_timestamp_millis(millis) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } - self.timestamp_opt(secs, millis as u32 * 1_000_000) } /// Makes a new `DateTime` from the number of non-leap nanoseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// Unlike [`timestamp_millis`](#method.timestamp_millis), this never - /// panics. + /// Unlike [`timestamp_millis_opt`](#method.timestamp_millis_opt), this never fails. /// /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone}; + /// use chrono::{TimeZone, Utc}; /// /// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648); /// ``` fn timestamp_nanos(&self, nanos: i64) -> DateTime { - let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000); - if nanos < 0 { - secs -= 1; - nanos += 1_000_000_000; + self.from_utc_datetime(&DateTime::from_timestamp_nanos(nanos).naive_utc()) + } + + /// Makes a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). + /// + /// # Example + /// + /// ``` + /// use chrono::{TimeZone, Utc}; + /// + /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); + /// ``` + fn timestamp_micros(&self, micros: i64) -> MappedLocalTime> { + match DateTime::from_timestamp_micros(micros) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } - self.timestamp_opt(secs, nanos as u32).unwrap() } /// Parses a string with the specified format string and returns a /// `DateTime` with the current offset. /// - /// See the [`::format::strftime`] module on the + /// See the [`crate::format::strftime`] module on the /// supported escape sequences. /// /// If the to-be-parsed string includes an offset, it *must* match the @@ -414,6 +548,14 @@ pub trait TimeZone: Sized + Clone { /// /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. + /// + /// See also [`NaiveDateTime::parse_from_str`] which gives a [`NaiveDateTime`] without + /// an offset, but can be converted to a [`DateTime`] with [`NaiveDateTime::and_utc`] or + /// [`NaiveDateTime::and_local_timezone`]. + #[deprecated( + since = "0.4.29", + note = "use `DateTime::parse_from_str` or `NaiveDateTime::parse_from_str` with `and_utc()` or `and_local_timezone()` instead" + )] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; @@ -424,13 +566,16 @@ pub trait TimeZone: Sized + Clone { fn from_offset(offset: &Self::Offset) -> Self; /// Creates the offset(s) for given local `NaiveDate` if possible. - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult; + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime; /// Creates the offset(s) for given local `NaiveDateTime` if possible. - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")] + #[allow(deprecated)] + fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected Date::from_utc(*local, offset) @@ -438,9 +583,13 @@ pub trait TimeZone: Sized + Clone { } /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - self.offset_from_local_datetime(local) - .map(|offset| DateTime::from_utc(*local - offset.fix(), offset)) + #[allow(clippy::wrong_self_convention)] + fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime> { + self.offset_from_local_datetime(local).and_then(|off| { + local + .checked_sub_offset(off.fix()) + .map(|dt| DateTime::from_naive_utc_and_offset(dt, off)) + }) } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. @@ -451,48 +600,68 @@ pub trait TimeZone: Sized + Clone { /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")] + #[allow(deprecated)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { Date::from_utc(*utc, self.offset_from_utc_date(utc)) } /// Converts the UTC `NaiveDateTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc)) + DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc)) } } -mod fixed; -#[cfg(feature = "clock")] -mod local; -mod utc; - -pub use self::fixed::FixedOffset; -#[cfg(feature = "clock")] -pub use self::local::Local; -pub use self::utc::Utc; - #[cfg(test)] mod tests { use super::*; + #[test] + fn test_fixed_offset_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = offset.from_utc_datetime(&NaiveDateTime::MAX); + assert_eq!(local_max.naive_utc(), NaiveDateTime::MAX); + let local_min = offset.from_utc_datetime(&NaiveDateTime::MIN); + assert_eq!(local_min.naive_utc(), NaiveDateTime::MIN); + + let local_max = offset.from_local_datetime(&NaiveDateTime::MAX); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, MappedLocalTime::None); + } + let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, MappedLocalTime::None); + } + } + } + #[test] fn test_negative_millis() { - let dt = Utc.timestamp_millis(-1000); + let dt = Utc.timestamp_millis_opt(-1000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); - let dt = Utc.timestamp_millis(-7000); + let dt = Utc.timestamp_millis_opt(-7000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC"); - let dt = Utc.timestamp_millis(-7001); + let dt = Utc.timestamp_millis_opt(-7001).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.999 UTC"); - let dt = Utc.timestamp_millis(-7003); + let dt = Utc.timestamp_millis_opt(-7003).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC"); - let dt = Utc.timestamp_millis(-999); + let dt = Utc.timestamp_millis_opt(-999).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC"); - let dt = Utc.timestamp_millis(-1); + let dt = Utc.timestamp_millis_opt(-1).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC"); - let dt = Utc.timestamp_millis(-60000); + let dt = Utc.timestamp_millis_opt(-60000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); - let dt = Utc.timestamp_millis(-3600000); + let dt = Utc.timestamp_millis_opt(-3600000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); for (millis, expected) in &[ @@ -501,10 +670,10 @@ mod tests { (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis_opt(*millis) { - LocalResult::Single(dt) => { + MappedLocalTime::Single(dt) => { assert_eq!(dt.to_string(), *expected); } - e => panic!("Got {:?} instead of an okay answer", e), + e => panic!("Got {e:?} instead of an okay answer"), } } } @@ -525,8 +694,22 @@ mod tests { #[test] fn test_nanos_never_panics() { - Utc.timestamp_nanos(i64::max_value()); + Utc.timestamp_nanos(i64::MAX); Utc.timestamp_nanos(i64::default()); - Utc.timestamp_nanos(i64::min_value()); + Utc.timestamp_nanos(i64::MIN); + } + + #[test] + fn test_negative_micros() { + let dt = Utc.timestamp_micros(-1_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); + let dt = Utc.timestamp_micros(-999_999).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000001 UTC"); + let dt = Utc.timestamp_micros(-1).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999 UTC"); + let dt = Utc.timestamp_micros(-60_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); + let dt = Utc.timestamp_micros(-3_600_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); } } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index b094e57c4d..29e3090721 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -4,16 +4,24 @@ //! The UTC (Coordinated Universal Time) time zone. use core::fmt; - -use super::{FixedOffset, LocalResult, Offset, TimeZone}; -use naive::{NaiveDate, NaiveDateTime}; #[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) + feature = "now", + not(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) + )) ))] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "clock")] -use {Date, DateTime}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::{FixedOffset, MappedLocalTime, Offset, TimeZone}; +use crate::naive::{NaiveDate, NaiveDateTime}; +#[cfg(feature = "now")] +#[allow(deprecated)] +use crate::{Date, DateTime}; /// The UTC time zone. This is the most efficient time zone when you don't need the local time. /// It is also used as an offset (which is also a dummy type). @@ -25,34 +33,78 @@ use {Date, DateTime}; /// # Example /// /// ``` -/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; +/// use chrono::{DateTime, TimeZone, Utc}; /// -/// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); +/// let dt = DateTime::from_timestamp(61, 0).unwrap(); /// -/// assert_eq!(Utc.timestamp(61, 0), dt); -/// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt); +/// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); +/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub struct Utc; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] impl Utc { /// Returns a `Date` which corresponds to the current date. + #[deprecated( + since = "0.4.23", + note = "use `Utc::now()` instead, potentially with `.date_naive()`" + )] + #[allow(deprecated)] + #[must_use] pub fn today() -> Date { Utc::now().date() } - /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + /// Returns a `DateTime` which corresponds to the current date and time in UTC. + /// + /// See also the similar [`Local::now()`] which returns `DateTime`, i.e. the local date + /// and time including offset from UTC. + /// + /// [`Local::now()`]: crate::Local::now + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{FixedOffset, Utc}; + /// // Current time in UTC + /// let now_utc = Utc::now(); + /// + /// // Current date in UTC + /// let today_utc = now_utc.date_naive(); + /// + /// // Current time in some timezone (let's use +05:00) + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Utc::now().with_timezone(&offset); + /// ``` + #[cfg(not(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) + )))] + #[must_use] pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - let naive = NaiveDateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos() as u32); - DateTime::from_utc(naive, Utc) + DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()).unwrap() } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + #[cfg(all( + target_family = "wasm", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) + ))] + #[must_use] pub fn now() -> DateTime { let now = js_sys::Date::new_0(); DateTime::::from(now) @@ -66,11 +118,11 @@ impl TimeZone for Utc { Utc } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc { @@ -83,7 +135,7 @@ impl TimeZone for Utc { impl Offset for Utc { fn fix(&self) -> FixedOffset { - FixedOffset::east(0) + FixedOffset::east_opt(0).unwrap() } } @@ -98,3 +150,10 @@ impl fmt::Display for Utc { write!(f, "UTC") } } + +#[cfg(feature = "defmt")] +impl defmt::Format for Utc { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "Z"); + } +} diff --git a/src/oldtime.rs b/src/oldtime.rs deleted file mode 100644 index 388b71a47d..0000000000 --- a/src/oldtime.rs +++ /dev/null @@ -1,688 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Temporal quantification - -use core::ops::{Add, Div, Mul, Neg, Sub}; -use core::time::Duration as StdDuration; -use core::{fmt, i64}; -#[cfg(any(feature = "std", test))] -use std::error::Error; - -/// The number of nanoseconds in a microsecond. -const NANOS_PER_MICRO: i32 = 1000; -/// The number of nanoseconds in a millisecond. -const NANOS_PER_MILLI: i32 = 1000_000; -/// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; -/// The number of microseconds per second. -const MICROS_PER_SEC: i64 = 1000_000; -/// The number of milliseconds per second. -const MILLIS_PER_SEC: i64 = 1000; -/// The number of seconds in a minute. -const SECS_PER_MINUTE: i64 = 60; -/// The number of seconds in an hour. -const SECS_PER_HOUR: i64 = 3600; -/// The number of (non-leap) seconds in days. -const SECS_PER_DAY: i64 = 86400; -/// The number of (non-leap) seconds in a week. -const SECS_PER_WEEK: i64 = 604800; - -macro_rules! try_opt { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - -/// ISO 8601 time duration with nanosecond precision. -/// -/// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct Duration { - secs: i64, - nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC -} - -/// The minimum possible `Duration`: `i64::MIN` milliseconds. -pub const MIN: Duration = Duration { - secs: i64::MIN / MILLIS_PER_SEC - 1, - nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, -}; - -/// The maximum possible `Duration`: `i64::MAX` milliseconds. -pub const MAX: Duration = Duration { - secs: i64::MAX / MILLIS_PER_SEC, - nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, -}; - -impl Duration { - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of hours. - /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of minutes. - /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of seconds. - /// Panics when the duration is more than `i64::MAX` seconds - /// or less than `i64::MIN` seconds. - #[inline] - pub fn seconds(seconds: i64) -> Duration { - let d = Duration { secs: seconds, nanos: 0 }; - if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); - } - d - } - - /// Makes a new `Duration` with given number of milliseconds. - #[inline] - pub fn milliseconds(milliseconds: i64) -> Duration { - let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs: secs, nanos: nanos } - } - - /// Makes a new `Duration` with given number of microseconds. - #[inline] - pub fn microseconds(microseconds: i64) -> Duration { - let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - let nanos = micros as i32 * NANOS_PER_MICRO; - Duration { secs: secs, nanos: nanos } - } - - /// Makes a new `Duration` with given number of nanoseconds. - #[inline] - pub fn nanoseconds(nanos: i64) -> Duration { - let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); - Duration { secs: secs, nanos: nanos as i32 } - } - - /// Returns the total number of whole weeks in the duration. - #[inline] - pub fn num_weeks(&self) -> i64 { - self.num_days() / 7 - } - - /// Returns the total number of whole days in the duration. - pub fn num_days(&self) -> i64 { - self.num_seconds() / SECS_PER_DAY - } - - /// Returns the total number of whole hours in the duration. - #[inline] - pub fn num_hours(&self) -> i64 { - self.num_seconds() / SECS_PER_HOUR - } - - /// Returns the total number of whole minutes in the duration. - #[inline] - pub fn num_minutes(&self) -> i64 { - self.num_seconds() / SECS_PER_MINUTE - } - - /// Returns the total number of whole seconds in the duration. - pub fn num_seconds(&self) -> i64 { - // If secs is negative, nanos should be subtracted from the duration. - if self.secs < 0 && self.nanos > 0 { - self.secs + 1 - } else { - self.secs - } - } - - /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the duration. - fn nanos_mod_sec(&self) -> i32 { - if self.secs < 0 && self.nanos > 0 { - self.nanos - NANOS_PER_SEC - } else { - self.nanos - } - } - - /// Returns the total number of whole milliseconds in the duration, - pub fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined - // such that the range is exactly i64 milliseconds. - let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; - secs_part + nanos_part as i64 - } - - /// Returns the total number of whole microseconds in the duration, - /// or `None` on overflow (exceeding 2^63 microseconds in either direction). - pub fn num_microseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; - secs_part.checked_add(nanos_part as i64) - } - - /// Returns the total number of whole nanoseconds in the duration, - /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). - pub fn num_nanoseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); - let nanos_part = self.nanos_mod_sec(); - secs_part.checked_add(nanos_part as i64) - } - - /// Add two durations, returning `None` if overflow occurred. - pub fn checked_add(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs = try_opt!(secs.checked_add(1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) - } - } - - /// Subtract two durations, returning `None` if overflow occurred. - pub fn checked_sub(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs = try_opt!(secs.checked_sub(1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) - } - } - - /// Returns the duration as an absolute (non-negative) value. - #[inline] - pub fn abs(&self) -> Duration { - Duration { secs: self.secs.abs(), nanos: self.nanos } - } - - /// The minimum possible `Duration`: `i64::MIN` milliseconds. - #[inline] - pub fn min_value() -> Duration { - MIN - } - - /// The maximum possible `Duration`: `i64::MAX` milliseconds. - #[inline] - pub fn max_value() -> Duration { - MAX - } - - /// A duration where the stored seconds and nanoseconds are equal to zero. - #[inline] - pub fn zero() -> Duration { - Duration { secs: 0, nanos: 0 } - } - - /// Returns `true` if the duration equals `Duration::zero()`. - #[inline] - pub fn is_zero(&self) -> bool { - self.secs == 0 && self.nanos == 0 - } - - /// Creates a `time::Duration` object from `std::time::Duration` - /// - /// This function errors when original duration is larger than the maximum - /// value supported for this type. - pub fn from_std(duration: StdDuration) -> Result { - // We need to check secs as u64 before coercing to i64 - if duration.as_secs() > MAX.secs as u64 { - return Err(OutOfRangeError(())); - } - let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 }; - if d > MAX { - return Err(OutOfRangeError(())); - } - Ok(d) - } - - /// Creates a `std::time::Duration` object from `time::Duration` - /// - /// This function errors when duration is less than zero. As standard - /// library implementation is limited to non-negative values. - pub fn to_std(&self) -> Result { - if self.secs < 0 { - return Err(OutOfRangeError(())); - } - Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) - } -} - -impl Neg for Duration { - type Output = Duration; - - #[inline] - fn neg(self) -> Duration { - if self.nanos == 0 { - Duration { secs: -self.secs, nanos: 0 } - } else { - Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } - } - } -} - -impl Add for Duration { - type Output = Duration; - - fn add(self, rhs: Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl Sub for Duration { - type Output = Duration; - - fn sub(self, rhs: Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl Mul for Duration { - type Output = Duration; - - fn mul(self, rhs: i32) -> Duration { - // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanos as i64 * rhs as i64; - let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); - let secs = self.secs * rhs as i64 + extra_secs; - Duration { secs: secs, nanos: nanos as i32 } - } -} - -impl Div for Duration { - type Output = Duration; - - fn div(self, rhs: i32) -> Duration { - let mut secs = self.secs / rhs as i64; - let carry = self.secs - secs * rhs as i64; - let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; - let mut nanos = self.nanos / rhs + extra_nanos as i32; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl fmt::Display for Duration { - /// Format a duration using the [ISO 8601] format - /// - /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // technically speaking, negative duration is not valid ISO 8601, - // but we need to print it anyway. - let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; - - let days = abs.secs / SECS_PER_DAY; - let secs = abs.secs - days * SECS_PER_DAY; - let hasdate = days != 0; - let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; - - write!(f, "{}P", sign)?; - - if hasdate { - write!(f, "{}D", days)?; - } - if hastime { - if abs.nanos == 0 { - write!(f, "T{}S", secs)?; - } else if abs.nanos % NANOS_PER_MILLI == 0 { - write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?; - } else if abs.nanos % NANOS_PER_MICRO == 0 { - write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?; - } else { - write!(f, "T{}.{:09}S", secs, abs.nanos)?; - } - } - Ok(()) - } -} - -/// Represents error when converting `Duration` to/from a standard library -/// implementation -/// -/// The `std::time::Duration` supports a range from zero to `u64::MAX` -/// *seconds*, while this module supports signed range of up to -/// `i64::MAX` of *milliseconds*. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct OutOfRangeError(()); - -impl fmt::Display for OutOfRangeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Source duration value is out of range for the target type") - } -} - -#[cfg(any(feature = "std", test))] -impl Error for OutOfRangeError { - #[allow(deprecated)] - fn description(&self) -> &str { - "out of range error" - } -} - -// Copied from libnum -#[inline] -fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { - (div_floor_64(this, other), mod_floor_64(this, other)) -} - -#[inline] -fn div_floor_64(this: i64, other: i64) -> i64 { - match div_rem_64(this, other) { - (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, - (d, _) => d, - } -} - -#[inline] -fn mod_floor_64(this: i64, other: i64) -> i64 { - match this % other { - r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, - r => r, - } -} - -#[inline] -fn div_rem_64(this: i64, other: i64) -> (i64, i64) { - (this / other, this % other) -} - -#[cfg(test)] -mod tests { - use super::{Duration, OutOfRangeError, MAX, MIN}; - use std::time::Duration as StdDuration; - use std::{i32, i64}; - - #[test] - fn test_duration() { - assert!(Duration::seconds(1) != Duration::zero()); - assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); - assert_eq!( - Duration::seconds(86399) + Duration::seconds(4), - Duration::days(1) + Duration::seconds(3) - ); - assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000)); - assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000)); - assert_eq!( - Duration::days(2) + Duration::seconds(86399) + Duration::nanoseconds(1234567890), - Duration::days(3) + Duration::nanoseconds(234567890) - ); - assert_eq!(-Duration::days(3), Duration::days(-3)); - assert_eq!( - -(Duration::days(3) + Duration::seconds(70)), - Duration::days(-4) + Duration::seconds(86400 - 70) - ); - } - - #[test] - fn test_duration_num_days() { - assert_eq!(Duration::zero().num_days(), 0); - assert_eq!(Duration::days(1).num_days(), 1); - assert_eq!(Duration::days(-1).num_days(), -1); - assert_eq!(Duration::seconds(86399).num_days(), 0); - assert_eq!(Duration::seconds(86401).num_days(), 1); - assert_eq!(Duration::seconds(-86399).num_days(), 0); - assert_eq!(Duration::seconds(-86401).num_days(), -1); - assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); - assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); - } - - #[test] - fn test_duration_num_seconds() { - assert_eq!(Duration::zero().num_seconds(), 0); - assert_eq!(Duration::seconds(1).num_seconds(), 1); - assert_eq!(Duration::seconds(-1).num_seconds(), -1); - assert_eq!(Duration::milliseconds(999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(1001).num_seconds(), 1); - assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); - } - - #[test] - fn test_duration_num_milliseconds() { - assert_eq!(Duration::zero().num_milliseconds(), 0); - assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1); - assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1); - assert_eq!(Duration::microseconds(999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); - assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); - assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN); - assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), i64::MIN); - } - - #[test] - fn test_duration_num_microseconds() { - assert_eq!(Duration::zero().num_microseconds(), Some(0)); - assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1)); - assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); - assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); - assert_eq!(MAX.num_microseconds(), None); - assert_eq!(MIN.num_microseconds(), None); - - // overflow checks - const MICROS_PER_DAY: i64 = 86400_000_000; - assert_eq!( - Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), - Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) - ); - assert_eq!( - Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), - Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY) - ); - assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); - } - - #[test] - fn test_duration_num_nanoseconds() { - assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); - assert_eq!(MAX.num_nanoseconds(), None); - assert_eq!(MIN.num_nanoseconds(), None); - - // overflow checks - const NANOS_PER_DAY: i64 = 86400_000_000_000; - assert_eq!( - Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) - ); - assert_eq!( - Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY) - ); - assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); - } - - #[test] - fn test_duration_checked_ops() { - assert_eq!( - Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), - Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)) - ); - assert!(Duration::milliseconds(i64::MAX) - .checked_add(&Duration::microseconds(1000)) - .is_none()); - - assert_eq!( - Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)), - Some(Duration::milliseconds(i64::MIN)) - ); - assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none()); - } - - #[test] - fn test_duration_mul() { - assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() * i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 0, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 1, Duration::nanoseconds(1)); - assert_eq!(Duration::nanoseconds(1) * 1_000_000_000, Duration::seconds(1)); - assert_eq!(Duration::nanoseconds(1) * -1_000_000_000, -Duration::seconds(1)); - assert_eq!(-Duration::nanoseconds(1) * 1_000_000_000, -Duration::seconds(1)); - assert_eq!( - Duration::nanoseconds(30) * 333_333_333, - Duration::seconds(10) - Duration::nanoseconds(10) - ); - assert_eq!( - (Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3, - Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3) - ); - assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3)); - assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3)); - } - - #[test] - fn test_duration_div() { - assert_eq!(Duration::zero() / i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() / i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(123_456_789) / 1, Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333)); - assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333)); - assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500)); - assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333)); - assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); - } - - #[test] - fn test_duration_fmt() { - assert_eq!(Duration::zero().to_string(), "PT0S"); - assert_eq!(Duration::days(42).to_string(), "P42D"); - assert_eq!(Duration::days(-42).to_string(), "-P42D"); - assert_eq!(Duration::seconds(42).to_string(), "PT42S"); - assert_eq!(Duration::milliseconds(42).to_string(), "PT0.042S"); - assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S"); - assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S"); - assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(), "P7DT6.543S"); - assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S"); - assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S"); - - // the format specifier should have no effect on `Duration` - assert_eq!( - format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)), - "P1DT2.345S" - ); - } - - #[test] - fn test_to_std() { - assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); - assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); - assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000))); - assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000))); - assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); - assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000))); - assert_eq!(Duration::seconds(-1).to_std(), Err(OutOfRangeError(()))); - assert_eq!(Duration::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); - } - - #[test] - fn test_from_std() { - assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); - assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0))); - assert_eq!( - Ok(Duration::milliseconds(123)), - Duration::from_std(StdDuration::new(0, 123000000)) - ); - assert_eq!( - Ok(Duration::milliseconds(123765)), - Duration::from_std(StdDuration::new(123, 765000000)) - ); - assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777))); - assert_eq!(Ok(MAX), Duration::from_std(StdDuration::new(9223372036854775, 807000000))); - assert_eq!( - Duration::from_std(StdDuration::new(9223372036854776, 0)), - Err(OutOfRangeError(())) - ); - assert_eq!( - Duration::from_std(StdDuration::new(9223372036854775, 807000001)), - Err(OutOfRangeError(())) - ); - } -} diff --git a/src/round.rs b/src/round.rs index a8eb60d0b4..34c4fe442c 100644 --- a/src/round.rs +++ b/src/round.rs @@ -1,17 +1,12 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. +//! Functionality for rounding or truncating a `DateTime` by a `TimeDelta`. + +use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike}; use core::cmp::Ordering; use core::fmt; -use core::marker::Sized; use core::ops::{Add, Sub}; -use datetime::DateTime; -use naive::NaiveDateTime; -use oldtime::Duration; -#[cfg(any(feature = "std", test))] -use std; -use TimeZone; -use Timelike; /// Extension trait for subsecond rounding or truncation to a maximum number /// of digits. Rounding can be used to decrease the error variance when @@ -26,8 +21,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -38,8 +37,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -48,7 +51,7 @@ pub trait SubsecRound { impl SubsecRound for T where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { fn round_subsecs(self, digits: u16) -> T { let span = span_for_digits(digits); @@ -56,9 +59,9 @@ where if delta_down > 0 { let delta_up = span - delta_down; if delta_up <= delta_down { - self + Duration::nanoseconds(delta_up.into()) + self + TimeDelta::nanoseconds(delta_up.into()) } else { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } } else { self // unchanged @@ -69,7 +72,7 @@ where let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } else { self // unchanged } @@ -77,7 +80,7 @@ where } // Return the maximum span in nanoseconds for the target number of digits. -fn span_for_digits(digits: u16) -> u32 { +const fn span_for_digits(digits: u16) -> u32 { // fast lookup form of: 10^(9-min(9,digits)) match digits { 0 => 1_000_000_000, @@ -93,100 +96,141 @@ fn span_for_digits(digits: u16) -> u32 { } } -/// Extension trait for rounding or truncating a DateTime by a Duration. +/// Extension trait for rounding or truncating a DateTime by a TimeDelta. /// /// # Limitations -/// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and -/// [`DateTime::timestamp_nanos`]. This means that they will fail if either the -/// `Duration` or the `DateTime` are too big to represented as nanoseconds. They -/// will also fail if the `Duration` is bigger than the timestamp. +/// Both rounding and truncating are done via [`TimeDelta::num_nanoseconds`] and +/// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the +/// `TimeDelta` or the `DateTime` are too big to represented as nanoseconds. They +/// will also fail if the `TimeDelta` is bigger than the timestamp, negative or zero. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std", test))] + #[cfg(feature = "std")] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std", test)))] + #[cfg(all(not(feature = "std"), feature = "core-error"))] + type Err: core::error::Error; + + /// Error that can occur in rounding or truncating + #[cfg(all(not(feature = "std"), not(feature = "core-error")))] type Err: fmt::Debug + fmt::Display; - /// Return a copy rounded by Duration. + /// Return a copy rounded by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!( - /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_round(Duration::days(1)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` - fn duration_round(self, duration: Duration) -> Result; + fn duration_round(self, duration: TimeDelta) -> Result; - /// Return a copy truncated by Duration. + /// Return a copy truncated by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!( - /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` - fn duration_trunc(self, duration: Duration) -> Result; -} + fn duration_trunc(self, duration: TimeDelta) -> Result; -/// The maximum number of seconds a DateTime can be to be represented as nanoseconds -const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; + /// Return a copy rounded **up** by TimeDelta. + /// + /// # Example + /// ``` rust + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::milliseconds(10)).unwrap().to_string(), + /// "2018-01-11 12:00:00.160 UTC" + /// ); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::hours(1)).unwrap().to_string(), + /// "2018-01-11 13:00:00 UTC" + /// ); + /// + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::days(1)).unwrap().to_string(), + /// "2018-01-12 00:00:00 UTC" + /// ); + /// ``` + fn duration_round_up(self, duration: TimeDelta) -> Result; +} impl DurationRound for DateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: TimeDelta) -> Result { duration_round(self.naive_local(), self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self.naive_local(), self, duration) } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self.naive_local(), self, duration) + } } impl DurationRound for NaiveDateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: TimeDelta) -> Result { duration_round(self, self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self, self, duration) } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self, self, duration) + } } fn duration_round( naive: NaiveDateTime, original: T, - duration: Duration, + duration: TimeDelta, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = naive.timestamp_nanos(); - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; let delta_down = stamp % span; if delta_down == 0 { Ok(original) @@ -197,9 +241,9 @@ where (span - delta_down, delta_down) }; if delta_up <= delta_down { - Ok(original + Duration::nanoseconds(delta_up)) + Ok(original + TimeDelta::nanoseconds(delta_up)) } else { - Ok(original - Duration::nanoseconds(delta_down)) + Ok(original - TimeDelta::nanoseconds(delta_down)) } } } else { @@ -210,56 +254,76 @@ where fn duration_trunc( naive: NaiveDateTime, original: T, - duration: Duration, + duration: TimeDelta, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); + } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let delta_down = stamp % span; + match delta_down.cmp(&0) { + Ordering::Equal => Ok(original), + Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)), + Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())), } - let stamp = naive.timestamp_nanos(); - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); + } else { + Err(RoundingError::DurationExceedsLimit) + } +} + +fn duration_round_up( + naive: NaiveDateTime, + original: T, + duration: TimeDelta, +) -> Result +where + T: Timelike + Add + Sub, +{ + if let Some(span) = duration.num_nanoseconds() { + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), - Ordering::Greater => Ok(original - Duration::nanoseconds(delta_down)), - Ordering::Less => Ok(original - Duration::nanoseconds(span - delta_down.abs())), + Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)), + Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())), } } else { Err(RoundingError::DurationExceedsLimit) } } -/// An error from rounding by `Duration` +/// An error from rounding by `TimeDelta` /// /// See: [`DurationRound`] #[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RoundingError { - /// Error when the Duration exceeds the Duration from or until the Unix epoch. - /// - /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(1970, 12, 12).and_hms(0, 0, 0); + /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch. /// - /// assert_eq!( - /// dt.duration_round(Duration::days(365)), - /// Err(RoundingError::DurationExceedsTimestamp), - /// ); - /// ``` + /// Note: this error is not produced anymore. DurationExceedsTimestamp, - /// Error when `Duration.num_nanoseconds` exceeds the limit. + /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2260, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000); + /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31) + /// .unwrap() + /// .and_hms_nano_opt(23, 59, 59, 1_75_500_000) + /// .unwrap() + /// .and_utc(); /// /// assert_eq!( - /// dt.duration_round(Duration::days(300 * 365)), + /// dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()), /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` @@ -268,10 +332,13 @@ pub enum RoundingError { /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2300, 12, 12).and_hms(0, 0, 0); + /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; + /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// - /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); + /// assert_eq!( + /// dt.duration_round(TimeDelta::try_days(1).unwrap()), + /// Err(RoundingError::TimestampExceedsLimit) + /// ); /// ``` TimestampExceedsLimit, } @@ -292,7 +359,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] impl std::error::Error for RoundingError { #[allow(deprecated)] fn description(&self) -> &str { @@ -300,16 +367,32 @@ impl std::error::Error for RoundingError { } } +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for RoundingError { + #[allow(deprecated)] + fn description(&self) -> &str { + "error from rounding or truncating with DurationRound" + } +} + #[cfg(test)] mod tests { - use super::{Duration, DurationRound, SubsecRound}; - use offset::{FixedOffset, TimeZone, Utc}; - use Timelike; + use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; + use crate::Timelike; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::{DateTime, NaiveDate}; #[test] fn test_round_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(10), dt); assert_eq!(dt.round_subsecs(9), dt); @@ -325,7 +408,14 @@ mod tests { assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 13); - let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); @@ -338,7 +428,14 @@ mod tests { #[test] fn test_round_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); @@ -351,8 +448,15 @@ mod tests { #[test] fn test_trunc_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(10), dt); assert_eq!(dt.trunc_subsecs(9), dt); @@ -368,7 +472,14 @@ mod tests { assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).second(), 13); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); @@ -381,7 +492,14 @@ mod tests { #[test] fn test_trunc_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); @@ -394,221 +512,584 @@ mod tests { #[test] fn test_duration_round() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); + + assert_eq!( + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); // round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-13 00:00:00 UTC" ); // timezone east - let dt = FixedOffset::east(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-29 00:00:00 +01:00" ); // timezone west - let dt = FixedOffset::west(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-29 00:00:00 -01:00" ); } #[test] fn test_duration_round_naive() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.180" ); // round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:25:00" ); // round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap() + .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:30:00" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-13 00:00:00" ); } #[test] fn test_duration_round_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } #[test] fn test_duration_trunc() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" ); // would round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-12 00:00:00 UTC" ); // timezone east - let dt = FixedOffset::east(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-22 00:00:00 +01:00" ); // timezone west - let dt = FixedOffset::west(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-22 00:00:00 -01:00" ); } #[test] fn test_duration_trunc_naive() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170" ); // would round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); // would round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999).naive_utc(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap() + .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-12 00:00:00" ); } #[test] fn test_duration_trunc_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } + + #[test] + fn issue1010() { + let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap(); + let span = TimeDelta::microseconds(-7_019_067_213_869_040); + assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap(); + let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap(); + let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + } + + #[test] + fn test_duration_trunc_close_to_epoch() { + let span = TimeDelta::try_minutes(15).unwrap(); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00"); + } + + #[test] + fn test_duration_round_close_to_epoch() { + let span = TimeDelta::try_minutes(15).unwrap(); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + } + + #[test] + fn test_duration_round_close_to_min_max() { + let span = TimeDelta::nanoseconds(i64::MAX); + + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "1677-09-21 00:12:43.145224193 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); + + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); + } + + #[test] + fn test_duration_round_up() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180 UTC" + ); + + // round up + let dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00 UTC" + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00 UTC" + ); + + // timezone east + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 +01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 +01:00" + ); + + // timezone west + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 -01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 -01:00" + ); + } + + #[test] + fn test_duration_round_up_naive() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180" + ); + + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00" + ); + } + + #[test] + fn test_duration_round_up_pre_epoch() { + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "1969-12-12 12:20:00 UTC" + ); + + let time_delta = TimeDelta::minutes(30); + assert_eq!( + DateTime::UNIX_EPOCH.duration_round_up(time_delta).unwrap().to_string(), + "1970-01-01 00:00:00 UTC" + ) + } + + #[test] + fn test_duration_round_up_close_to_min_max() { + let mut dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + let span = TimeDelta::nanoseconds(i64::MAX); + + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + DateTime::from_timestamp_nanos(i64::MAX).to_string() + ); + + dt = DateTime::UNIX_EPOCH + TimeDelta::nanoseconds(1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::from_timestamp_nanos(i64::MAX)); + + let dt = DateTime::from_timestamp_nanos(1); + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(-1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + + // Rounds to 1677-09-21 00:12:43.145224193 UTC if at i64::MIN. + // because i64::MIN is 1677-09-21 00:12:43.145224192 UTC. + // + // v + // We add 2 to get to 1677-09-21 00:12:43.145224194 UTC + // this issue is because abs(i64::MIN) == i64::MAX + 1 + let dt = DateTime::from_timestamp_nanos(i64::MIN + 2); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + } } diff --git a/src/sys.rs b/src/sys.rs deleted file mode 100644 index 0fa67838eb..0000000000 --- a/src/sys.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Platform wrappers for converting UTC times to and from the local time zone. -//! -//! This code was rescued from v0.1 of the time crate, which is no longer -//! maintained. It has been substantially stripped down to the bare minimum -//! required by chrono. - -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(any(target_arch = "wasm32", target_env = "sgx"))] -#[path = "sys/stub.rs"] -mod inner; - -#[cfg(all(unix, not(target_arch = "wasm32"), not(target_env = "sgx")))] -#[path = "sys/unix.rs"] -mod inner; - -#[cfg(all(windows, not(target_arch = "wasm32"), not(target_env = "sgx")))] -#[path = "sys/windows.rs"] -mod inner; - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -pub struct Timespec { - pub sec: i64, - pub nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - pub fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - - /// Converts this timespec into the system's local time. - pub fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - inner::time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[cfg(feature = "clock")] -#[repr(C)] -pub struct Tm { - /// Seconds after the minute - [0, 60] - pub tm_sec: i32, - - /// Minutes after the hour - [0, 59] - pub tm_min: i32, - - /// Hours after midnight - [0, 23] - pub tm_hour: i32, - - /// Day of the month - [1, 31] - pub tm_mday: i32, - - /// Months since January - [0, 11] - pub tm_mon: i32, - - /// Years since 1900 - pub tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - pub tm_wday: i32, - - /// Days since January 1 - [0, 365] - pub tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - pub tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - pub tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - pub tm_nsec: i32, -} - -impl Tm { - /// Convert time to the seconds from January 1, 1970 - pub fn to_timespec(&self) -> Timespec { - let sec = match self.tm_utcoff { - 0 => inner::utc_tm_to_time(self), - _ => inner::local_tm_to_time(self), - }; - Timespec { sec: sec, nsec: self.tm_nsec } - } -} diff --git a/src/sys/stub.rs b/src/sys/stub.rs deleted file mode 100644 index 9172a85223..0000000000 --- a/src/sys/stub.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::Tm; - -fn time_to_tm(ts: i64, tm: &mut Tm) { - let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }; - - static YTAB: [[i64; 12]; 2] = [ - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - ]; - - let mut year = 1970; - - let dayclock = ts % 86400; - let mut dayno = ts / 86400; - - tm.tm_sec = (dayclock % 60) as i32; - tm.tm_min = ((dayclock % 3600) / 60) as i32; - tm.tm_hour = (dayclock / 3600) as i32; - tm.tm_wday = ((dayno + 4) % 7) as i32; - loop { - let yearsize = if leapyear(year) { 366 } else { 365 }; - if dayno >= yearsize { - dayno -= yearsize; - year += 1; - } else { - break; - } - } - tm.tm_year = (year - 1900) as i32; - tm.tm_yday = dayno as i32; - let mut mon = 0; - while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] { - dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon]; - mon += 1; - } - tm.tm_mon = mon as i32; - tm.tm_mday = dayno as i32 + 1; - tm.tm_isdst = 0; -} - -fn tm_to_time(tm: &Tm) -> i64 { - let mut y = tm.tm_year as i64 + 1900; - let mut m = tm.tm_mon as i64 + 1; - if m <= 2 { - y -= 1; - m += 12; - } - let d = tm.tm_mday as i64; - let h = tm.tm_hour as i64; - let mi = tm.tm_min as i64; - let s = tm.tm_sec as i64; - (365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400 - + 3600 * h - + 60 * mi - + s -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - // FIXME: Add timezone logic - time_to_tm(sec, tm); -} - -pub fn utc_tm_to_time(tm: &Tm) -> i64 { - tm_to_time(tm) -} - -pub fn local_tm_to_time(tm: &Tm) -> i64 { - // FIXME: Add timezone logic - tm_to_time(tm) -} diff --git a/src/sys/unix.rs b/src/sys/unix.rs deleted file mode 100644 index 2f845e7455..0000000000 --- a/src/sys/unix.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::Tm; -use libc::{self, time_t}; -use std::io; -use std::mem; - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -extern "C" { - static timezone: time_t; - static altzone: time_t; -} - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -fn tzset() { - extern "C" { - fn tzset(); - } - unsafe { tzset() } -} - -fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) { - tm.tm_sec = rust_tm.tm_sec; - tm.tm_min = rust_tm.tm_min; - tm.tm_hour = rust_tm.tm_hour; - tm.tm_mday = rust_tm.tm_mday; - tm.tm_mon = rust_tm.tm_mon; - tm.tm_year = rust_tm.tm_year; - tm.tm_wday = rust_tm.tm_wday; - tm.tm_yday = rust_tm.tm_yday; - tm.tm_isdst = rust_tm.tm_isdst; -} - -fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) { - rust_tm.tm_sec = tm.tm_sec; - rust_tm.tm_min = tm.tm_min; - rust_tm.tm_hour = tm.tm_hour; - rust_tm.tm_mday = tm.tm_mday; - rust_tm.tm_mon = tm.tm_mon; - rust_tm.tm_year = tm.tm_year; - rust_tm.tm_wday = tm.tm_wday; - rust_tm.tm_yday = tm.tm_yday; - rust_tm.tm_isdst = tm.tm_isdst; - rust_tm.tm_utcoff = utcoff; -} - -#[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] -unsafe fn timegm(tm: *mut libc::tm) -> time_t { - use std::env::{remove_var, set_var, var_os}; - extern "C" { - fn tzset(); - } - - let ret; - - let current_tz = var_os("TZ"); - set_var("TZ", "UTC"); - tzset(); - - ret = libc::mktime(tm); - - if let Some(tz) = current_tz { - set_var("TZ", tz); - } else { - remove_var("TZ"); - } - tzset(); - - ret -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - unsafe { - let sec = sec as time_t; - let mut out = mem::zeroed(); - if libc::localtime_r(&sec, &mut out).is_null() { - panic!("localtime_r failed: {}", io::Error::last_os_error()); - } - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - let gmtoff = { - tzset(); - // < 0 means we don't know; assume we're not in DST. - if out.tm_isdst == 0 { - // timezone is seconds west of UTC, tm_gmtoff is seconds east - -timezone - } else if out.tm_isdst > 0 { - -altzone - } else { - -timezone - } - }; - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - let gmtoff = out.tm_gmtoff; - tm_to_rust_tm(&out, gmtoff as i32, tm); - } -} - -pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 { - #[cfg(not(any( - all(target_os = "android", target_pointer_width = "32"), - target_os = "nacl", - target_os = "solaris", - target_os = "illumos" - )))] - use libc::timegm; - #[cfg(all(target_os = "android", target_pointer_width = "32"))] - use libc::timegm64 as timegm; - - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); - unsafe { timegm(&mut tm) as i64 } -} - -pub fn local_tm_to_time(rust_tm: &Tm) -> i64 { - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); - unsafe { libc::mktime(&mut tm) as i64 } -} diff --git a/src/sys/windows.rs b/src/sys/windows.rs deleted file mode 100644 index 3f90338e41..0000000000 --- a/src/sys/windows.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::Tm; -use std::io; -use std::mem; - -use winapi::shared::minwindef::*; -use winapi::um::minwinbase::SYSTEMTIME; -use winapi::um::timezoneapi::*; - -const HECTONANOSECS_IN_SEC: i64 = 10_000_000; -const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; - -fn time_to_file_time(sec: i64) -> FILETIME { - let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64; - FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD } -} - -fn file_time_as_u64(ft: &FILETIME) -> u64 { - ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64) -} - -fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 { - let t = file_time_as_u64(ft) as i64; - ((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64 -} - -fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME { - unsafe { - let mut ft = mem::zeroed(); - SystemTimeToFileTime(sys, &mut ft); - ft - } -} - -fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME { - let mut sys: SYSTEMTIME = unsafe { mem::zeroed() }; - sys.wSecond = tm.tm_sec as WORD; - sys.wMinute = tm.tm_min as WORD; - sys.wHour = tm.tm_hour as WORD; - sys.wDay = tm.tm_mday as WORD; - sys.wDayOfWeek = tm.tm_wday as WORD; - sys.wMonth = (tm.tm_mon + 1) as WORD; - sys.wYear = (tm.tm_year + 1900) as WORD; - sys -} - -fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) { - tm.tm_sec = sys.wSecond as i32; - tm.tm_min = sys.wMinute as i32; - tm.tm_hour = sys.wHour as i32; - tm.tm_mday = sys.wDay as i32; - tm.tm_wday = sys.wDayOfWeek as i32; - tm.tm_mon = (sys.wMonth - 1) as i32; - tm.tm_year = (sys.wYear - 1900) as i32; - tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday); - - fn yday(year: i32, month: i32, day: i32) -> i32 { - let leap = if month > 2 { - if year % 4 == 0 { - 1 - } else { - 2 - } - } else { - 0 - }; - let july = if month > 7 { 1 } else { 0 }; - - (month - 1) * 30 + month / 2 + (day - 1) - leap + july - } -} - -macro_rules! call { - ($name:ident($($arg:expr),*)) => { - if $name($($arg),*) == 0 { - panic!(concat!(stringify!($name), " failed with: {}"), - io::Error::last_os_error()); - } - } -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - let ft = time_to_file_time(sec); - unsafe { - let mut utc = mem::zeroed(); - let mut local = mem::zeroed(); - call!(FileTimeToSystemTime(&ft, &mut utc)); - call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local)); - system_time_to_tm(&local, tm); - - let local = system_time_to_file_time(&local); - let local_sec = file_time_to_unix_seconds(&local); - - let mut tz = mem::zeroed(); - GetTimeZoneInformation(&mut tz); - - // SystemTimeToTzSpecificLocalTime already applied the biases so - // check if it non standard - tm.tm_utcoff = (local_sec - sec) as i32; - tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 }; - } -} - -pub fn utc_tm_to_time(tm: &Tm) -> i64 { - unsafe { - let mut ft = mem::zeroed(); - let sys_time = tm_to_system_time(tm); - call!(SystemTimeToFileTime(&sys_time, &mut ft)); - file_time_to_unix_seconds(&ft) - } -} - -pub fn local_tm_to_time(tm: &Tm) -> i64 { - unsafe { - let mut ft = mem::zeroed(); - let mut utc = mem::zeroed(); - let mut sys_time = tm_to_system_time(tm); - call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc)); - call!(SystemTimeToFileTime(&utc, &mut ft)); - file_time_to_unix_seconds(&ft) - } -} diff --git a/src/time_delta.rs b/src/time_delta.rs new file mode 100644 index 0000000000..a622ab818e --- /dev/null +++ b/src/time_delta.rs @@ -0,0 +1,1456 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Temporal quantification + +#[cfg(all(not(feature = "std"), feature = "core-error"))] +use core::error::Error; +use core::fmt; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; +use core::time::Duration; +#[cfg(feature = "std")] +use std::error::Error; + +use crate::{expect, try_opt}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// The number of nanoseconds in a microsecond. +const NANOS_PER_MICRO: i32 = 1000; +/// The number of nanoseconds in a millisecond. +const NANOS_PER_MILLI: i32 = 1_000_000; +/// The number of nanoseconds in seconds. +pub(crate) const NANOS_PER_SEC: i32 = 1_000_000_000; +/// The number of microseconds per second. +const MICROS_PER_SEC: i64 = 1_000_000; +/// The number of milliseconds per second. +const MILLIS_PER_SEC: i64 = 1000; +/// The number of seconds in a minute. +const SECS_PER_MINUTE: i64 = 60; +/// The number of seconds in an hour. +const SECS_PER_HOUR: i64 = 3600; +/// The number of (non-leap) seconds in days. +const SECS_PER_DAY: i64 = 86_400; +/// The number of (non-leap) seconds in a week. +const SECS_PER_WEEK: i64 = 604_800; + +/// Time duration with nanosecond precision. +/// +/// This also allows for negative durations; see individual methods for details. +/// +/// A `TimeDelta` is represented internally as a complement of seconds and +/// nanoseconds. The range is restricted to that of `i64` milliseconds, with the +/// minimum value notably being set to `-i64::MAX` rather than allowing the full +/// range of `i64::MIN`. This is to allow easy flipping of sign, so that for +/// instance `abs()` can be called without any checks. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimeDelta { + secs: i64, + nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC +} + +/// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. +pub(crate) const MIN: TimeDelta = TimeDelta { + secs: -i64::MAX / MILLIS_PER_SEC - 1, + nanos: NANOS_PER_SEC + (-i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, +}; + +/// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. +pub(crate) const MAX: TimeDelta = TimeDelta { + secs: i64::MAX / MILLIS_PER_SEC, + nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, +}; + +impl TimeDelta { + /// Makes a new `TimeDelta` with given number of seconds and nanoseconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000. + pub const fn new(secs: i64, nanos: u32) -> Option { + if secs < MIN.secs + || secs > MAX.secs + || nanos >= 1_000_000_000 + || (secs == MAX.secs && nanos > MAX.nanos as u32) + || (secs == MIN.secs && nanos < MIN.nanos as u32) + { + return None; + } + Some(TimeDelta { secs, nanos: nanos as i32 }) + } + + /// Makes a new `TimeDelta` with the given number of weeks. + /// + /// Equivalent to `TimeDelta::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds. + #[inline] + #[must_use] + #[track_caller] + pub const fn weeks(weeks: i64) -> TimeDelta { + expect(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of weeks. + /// + /// Equivalent to `TimeDelta::try_seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_weeks(weeks: i64) -> Option { + TimeDelta::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) + } + + /// Makes a new `TimeDelta` with the given number of days. + /// + /// Equivalent to `TimeDelta::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + #[track_caller] + pub const fn days(days: i64) -> TimeDelta { + expect(TimeDelta::try_days(days), "TimeDelta::days out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of days. + /// + /// Equivalent to `TimeDelta::try_seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_days(days: i64) -> Option { + TimeDelta::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) + } + + /// Makes a new `TimeDelta` with the given number of hours. + /// + /// Equivalent to `TimeDelta::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + #[track_caller] + pub const fn hours(hours: i64) -> TimeDelta { + expect(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of hours. + /// + /// Equivalent to `TimeDelta::try_seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_hours(hours: i64) -> Option { + TimeDelta::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) + } + + /// Makes a new `TimeDelta` with the given number of minutes. + /// + /// Equivalent to `TimeDelta::seconds(minutes * 60)` with overflow checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + #[track_caller] + pub const fn minutes(minutes: i64) -> TimeDelta { + expect(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of minutes. + /// + /// Equivalent to `TimeDelta::try_seconds(minutes * 60)` with overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_minutes(minutes: i64) -> Option { + TimeDelta::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) + } + + /// Makes a new `TimeDelta` with the given number of seconds. + /// + /// # Panics + /// + /// Panics when `seconds` is more than `i64::MAX / 1_000` or less than `-i64::MAX / 1_000` + /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). + #[inline] + #[must_use] + #[track_caller] + pub const fn seconds(seconds: i64) -> TimeDelta { + expect(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of seconds. + /// + /// # Errors + /// + /// Returns `None` when `seconds` is more than `i64::MAX / 1_000` or less than + /// `-i64::MAX / 1_000` (in this context, this is the same as `i64::MIN / 1_000` due to + /// rounding). + #[inline] + pub const fn try_seconds(seconds: i64) -> Option { + TimeDelta::new(seconds, 0) + } + + /// Makes a new `TimeDelta` with the given number of milliseconds. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than + /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. + #[inline] + #[track_caller] + pub const fn milliseconds(milliseconds: i64) -> TimeDelta { + expect(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of milliseconds. + /// + /// # Errors + /// + /// Returns `None` the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more + /// than `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. + #[inline] + pub const fn try_milliseconds(milliseconds: i64) -> Option { + // We don't need to compare against MAX, as this function accepts an + // i64, and MAX is aligned to i64::MAX milliseconds. + if milliseconds < -i64::MAX { + return None; + } + let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); + let d = TimeDelta { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + Some(d) + } + + /// Makes a new `TimeDelta` with the given number of microseconds. + /// + /// The number of microseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `TimeDelta`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. + #[inline] + pub const fn microseconds(microseconds: i64) -> TimeDelta { + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + let nanos = micros as i32 * NANOS_PER_MICRO; + TimeDelta { secs, nanos } + } + + /// Makes a new `TimeDelta` with the given number of nanoseconds. + /// + /// The number of nanoseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `TimeDelta`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. + #[inline] + pub const fn nanoseconds(nanos: i64) -> TimeDelta { + let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); + TimeDelta { secs, nanos: nanos as i32 } + } + + /// Returns the total number of whole weeks in the `TimeDelta`. + #[inline] + pub const fn num_weeks(&self) -> i64 { + self.num_days() / 7 + } + + /// Returns the total number of whole days in the `TimeDelta`. + #[inline] + pub const fn num_days(&self) -> i64 { + self.num_seconds() / SECS_PER_DAY + } + + /// Returns the total number of whole hours in the `TimeDelta`. + #[inline] + pub const fn num_hours(&self) -> i64 { + self.num_seconds() / SECS_PER_HOUR + } + + /// Returns the total number of whole minutes in the `TimeDelta`. + #[inline] + pub const fn num_minutes(&self) -> i64 { + self.num_seconds() / SECS_PER_MINUTE + } + + /// Returns the total number of whole seconds in the `TimeDelta`. + pub const fn num_seconds(&self) -> i64 { + // If secs is negative, nanos should be subtracted from the duration. + if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } + } + + /// Returns the fractional number of seconds in the `TimeDelta`. + pub fn as_seconds_f64(self) -> f64 { + self.secs as f64 + self.nanos as f64 / NANOS_PER_SEC as f64 + } + + /// Returns the fractional number of seconds in the `TimeDelta`. + pub fn as_seconds_f32(self) -> f32 { + self.secs as f32 + self.nanos as f32 / NANOS_PER_SEC as f32 + } + + /// Returns the total number of whole milliseconds in the `TimeDelta`. + pub const fn num_milliseconds(&self) -> i64 { + // A proper TimeDelta will not overflow, because MIN and MAX are defined such + // that the range is within the bounds of an i64, from -i64::MAX through to + // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. + let secs_part = self.num_seconds() * MILLIS_PER_SEC; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; + secs_part + nanos_part as i64 + } + + /// Returns the number of milliseconds in the fractional part of the duration. + /// + /// This is the number of milliseconds such that + /// `subsec_millis() + num_seconds() * 1_000` is the truncated number of + /// milliseconds in the duration. + pub const fn subsec_millis(&self) -> i32 { + self.subsec_nanos() / NANOS_PER_MILLI + } + + /// Returns the total number of whole microseconds in the `TimeDelta`, + /// or `None` on overflow (exceeding 2^63 microseconds in either direction). + pub const fn num_microseconds(&self) -> Option { + let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); + let nanos_part = self.subsec_nanos() / NANOS_PER_MICRO; + secs_part.checked_add(nanos_part as i64) + } + + /// Returns the number of microseconds in the fractional part of the duration. + /// + /// This is the number of microseconds such that + /// `subsec_micros() + num_seconds() * 1_000_000` is the truncated number of + /// microseconds in the duration. + pub const fn subsec_micros(&self) -> i32 { + self.subsec_nanos() / NANOS_PER_MICRO + } + + /// Returns the total number of whole nanoseconds in the `TimeDelta`, + /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). + pub const fn num_nanoseconds(&self) -> Option { + let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); + let nanos_part = self.subsec_nanos(); + secs_part.checked_add(nanos_part as i64) + } + + /// Returns the number of nanoseconds in the fractional part of the duration. + /// + /// This is the number of nanoseconds such that + /// `subsec_nanos() + num_seconds() * 1_000_000_000` is the total number of + /// nanoseconds in the `TimeDelta`. + pub const fn subsec_nanos(&self) -> i32 { + if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } + } + + /// Add two `TimeDelta`s, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_add(&self, rhs: &TimeDelta) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `TimeDelta::new`. + let mut secs = self.secs + rhs.secs; + let mut nanos = self.nanos + rhs.nanos; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs += 1; + } + TimeDelta::new(secs, nanos as u32) + } + + /// Subtract two `TimeDelta`s, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_sub(&self, rhs: &TimeDelta) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `TimeDelta::new`. + let mut secs = self.secs - rhs.secs; + let mut nanos = self.nanos - rhs.nanos; + if nanos < 0 { + nanos += NANOS_PER_SEC; + secs -= 1; + } + TimeDelta::new(secs, nanos as u32) + } + + /// Multiply a `TimeDelta` with a i32, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_mul(&self, rhs: i32) -> Option { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanos as i64 * rhs as i64; + let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); + // Multiply seconds as i128 to prevent overflow + let secs: i128 = self.secs as i128 * rhs as i128 + extra_secs as i128; + if secs <= i64::MIN as i128 || secs >= i64::MAX as i128 { + return None; + }; + Some(TimeDelta { secs: secs as i64, nanos: nanos as i32 }) + } + + /// Divide a `TimeDelta` with a i32, returning `None` if dividing by 0. + #[must_use] + pub const fn checked_div(&self, rhs: i32) -> Option { + if rhs == 0 { + return None; + } + let secs = self.secs / rhs as i64; + let carry = self.secs % rhs as i64; + let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; + let nanos = self.nanos / rhs + extra_nanos as i32; + + let (secs, nanos) = match nanos { + i32::MIN..=-1 => (secs - 1, nanos + NANOS_PER_SEC), + NANOS_PER_SEC..=i32::MAX => (secs + 1, nanos - NANOS_PER_SEC), + _ => (secs, nanos), + }; + + Some(TimeDelta { secs, nanos }) + } + + /// Returns the `TimeDelta` as an absolute (non-negative) value. + #[inline] + pub const fn abs(&self) -> TimeDelta { + if self.secs < 0 && self.nanos != 0 { + TimeDelta { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } + } else { + TimeDelta { secs: self.secs.abs(), nanos: self.nanos } + } + } + + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MIN` instead")] + #[inline] + pub const fn min_value() -> TimeDelta { + MIN + } + + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MAX` instead")] + #[inline] + pub const fn max_value() -> TimeDelta { + MAX + } + + /// A `TimeDelta` where the stored seconds and nanoseconds are equal to zero. + #[inline] + pub const fn zero() -> TimeDelta { + TimeDelta { secs: 0, nanos: 0 } + } + + /// Returns `true` if the `TimeDelta` equals `TimeDelta::zero()`. + #[inline] + pub const fn is_zero(&self) -> bool { + self.secs == 0 && self.nanos == 0 + } + + /// Creates a `TimeDelta` object from `std::time::Duration` + /// + /// This function errors when original duration is larger than the maximum + /// value supported for this type. + pub const fn from_std(duration: Duration) -> Result { + // We need to check secs as u64 before coercing to i64 + if duration.as_secs() > MAX.secs as u64 { + return Err(OutOfRangeError(())); + } + match TimeDelta::new(duration.as_secs() as i64, duration.subsec_nanos()) { + Some(d) => Ok(d), + None => Err(OutOfRangeError(())), + } + } + + /// Creates a `std::time::Duration` object from a `TimeDelta`. + /// + /// This function errors when duration is less than zero. As standard + /// library implementation is limited to non-negative values. + pub const fn to_std(&self) -> Result { + if self.secs < 0 { + return Err(OutOfRangeError(())); + } + Ok(Duration::new(self.secs as u64, self.nanos as u32)) + } + + /// This duplicates `Neg::neg` because trait methods can't be const yet. + pub(crate) const fn neg(self) -> TimeDelta { + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + TimeDelta { secs: -self.secs - secs_diff, nanos } + } + + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + pub const MIN: Self = MIN; + + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + pub const MAX: Self = MAX; +} + +impl Neg for TimeDelta { + type Output = TimeDelta; + + #[inline] + #[track_caller] + fn neg(self) -> TimeDelta { + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + TimeDelta { secs: -self.secs - secs_diff, nanos } + } +} + +impl Add for TimeDelta { + type Output = TimeDelta; + + #[track_caller] + fn add(self, rhs: TimeDelta) -> TimeDelta { + self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed") + } +} + +impl Sub for TimeDelta { + type Output = TimeDelta; + + #[track_caller] + fn sub(self, rhs: TimeDelta) -> TimeDelta { + self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed") + } +} + +impl AddAssign for TimeDelta { + #[track_caller] + fn add_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed"); + *self = new; + } +} + +impl SubAssign for TimeDelta { + #[track_caller] + fn sub_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed"); + *self = new; + } +} + +impl Mul for TimeDelta { + type Output = TimeDelta; + + #[track_caller] + fn mul(self, rhs: i32) -> TimeDelta { + self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed") + } +} + +impl Div for TimeDelta { + type Output = TimeDelta; + + #[track_caller] + fn div(self, rhs: i32) -> TimeDelta { + self.checked_div(rhs).expect("`i32` is zero") + } +} + +impl<'a> core::iter::Sum<&'a TimeDelta> for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + *x) + } +} + +impl core::iter::Sum for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + x) + } +} + +impl fmt::Display for TimeDelta { + /// Format a `TimeDelta` using the [ISO 8601] format + /// + /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // technically speaking, negative duration is not valid ISO 8601, + // but we need to print it anyway. + let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; + + write!(f, "{sign}P")?; + // Plenty of ways to encode an empty string. `P0D` is short and not too strange. + if abs.secs == 0 && abs.nanos == 0 { + return f.write_str("0D"); + } + + f.write_fmt(format_args!("T{}", abs.secs))?; + + if abs.nanos > 0 { + // Count the number of significant digits, while removing all trailing zero's. + let mut figures = 9usize; + let mut fraction_digits = abs.nanos; + loop { + let div = fraction_digits / 10; + let last_digit = fraction_digits % 10; + if last_digit != 0 { + break; + } + fraction_digits = div; + figures -= 1; + } + f.write_fmt(format_args!(".{fraction_digits:0figures$}"))?; + } + f.write_str("S")?; + Ok(()) + } +} + +/// Represents error when converting `TimeDelta` to/from a standard library +/// implementation +/// +/// The `std::time::Duration` supports a range from zero to `u64::MAX` +/// *seconds*, while this module supports signed range of up to +/// `i64::MAX` of *milliseconds*. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OutOfRangeError(()); + +impl fmt::Display for OutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Source duration value is out of range for the target type") + } +} + +#[cfg(any(feature = "std", feature = "core-error"))] +impl Error for OutOfRangeError { + #[allow(deprecated)] + fn description(&self) -> &str { + "out of range error" + } +} + +#[inline] +const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { + (this.div_euclid(other), this.rem_euclid(other)) +} + +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for TimeDelta { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; + const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; + + let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; + let nanos: i32 = u.int_in_range(0..=(NANOS_PER_SEC - 1))?; + let duration = TimeDelta { secs, nanos }; + + if duration < MIN || duration > MAX { + Err(arbitrary::Error::IncorrectFormat) + } else { + Ok(duration) + } + } +} + +#[cfg(feature = "serde")] +mod serde { + use super::TimeDelta; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + + impl Serialize for TimeDelta { + fn serialize(&self, serializer: S) -> Result { + <(i64, i32) as Serialize>::serialize(&(self.secs, self.nanos), serializer) + } + } + + impl<'de> Deserialize<'de> for TimeDelta { + fn deserialize>(deserializer: D) -> Result { + let (secs, nanos) = <(i64, i32) as Deserialize>::deserialize(deserializer)?; + TimeDelta::new(secs, nanos as u32).ok_or(Error::custom("TimeDelta out of bounds")) + } + } + + #[cfg(test)] + mod tests { + use super::{super::MAX, TimeDelta}; + + #[test] + fn test_serde() { + let duration = TimeDelta::new(123, 456).unwrap(); + assert_eq!( + serde_json::from_value::(serde_json::to_value(duration).unwrap()) + .unwrap(), + duration + ); + } + + #[test] + #[should_panic(expected = "TimeDelta out of bounds")] + fn test_serde_oob_panic() { + let _ = + serde_json::from_value::(serde_json::json!([MAX.secs + 1, 0])).unwrap(); + } + } +} + +#[cfg(test)] +mod tests { + use super::OutOfRangeError; + use super::{MAX, MIN, TimeDelta}; + use crate::expect; + use core::time::Duration; + + #[test] + fn test_duration() { + let days = |d| TimeDelta::try_days(d).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + assert!(seconds(1) != TimeDelta::zero()); + assert_eq!(seconds(1) + seconds(2), seconds(3)); + assert_eq!(seconds(86_399) + seconds(4), days(1) + seconds(3)); + assert_eq!(days(10) - seconds(1000), seconds(863_000)); + assert_eq!(days(10) - seconds(1_000_000), seconds(-136_000)); + assert_eq!( + days(2) + seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), + days(3) + TimeDelta::nanoseconds(234_567_890) + ); + assert_eq!(-days(3), days(-3)); + assert_eq!(-(days(3) + seconds(70)), days(-4) + seconds(86_400 - 70)); + + let mut d = TimeDelta::default(); + d += TimeDelta::try_minutes(1).unwrap(); + d -= seconds(30); + assert_eq!(d, seconds(30)); + } + + #[test] + fn test_duration_num_days() { + assert_eq!(TimeDelta::zero().num_days(), 0); + assert_eq!(TimeDelta::try_days(1).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_days(-1).unwrap().num_days(), -1); + assert_eq!(TimeDelta::try_seconds(86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_seconds(-86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().num_days(), -1); + assert_eq!(TimeDelta::try_days(i32::MAX as i64).unwrap().num_days(), i32::MAX as i64); + assert_eq!(TimeDelta::try_days(i32::MIN as i64).unwrap().num_days(), i32::MIN as i64); + } + + #[test] + fn test_duration_num_seconds() { + assert_eq!(TimeDelta::zero().num_seconds(), 0); + assert_eq!(TimeDelta::try_seconds(1).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().num_seconds(), -1); + assert_eq!(TimeDelta::try_milliseconds(999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(1001).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(-1001).unwrap().num_seconds(), -1); + } + + #[test] + fn test_duration_seconds_max_allowed() { + let duration = TimeDelta::try_seconds(i64::MAX / 1_000).unwrap(); + assert_eq!(duration.num_seconds(), i64::MAX / 1_000); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + + #[test] + fn test_duration_seconds_max_overflow() { + assert!(TimeDelta::try_seconds(i64::MAX / 1_000 + 1).is_none()); + } + + #[test] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] + fn test_duration_seconds_max_overflow_panic() { + let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); + } + + #[test] + fn test_duration_seconds_min_allowed() { + let duration = TimeDelta::try_seconds(i64::MIN / 1_000).unwrap(); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + + #[test] + fn test_duration_seconds_min_underflow() { + assert!(TimeDelta::try_seconds(-i64::MAX / 1_000 - 1).is_none()); + } + + #[test] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] + fn test_duration_seconds_min_underflow_panic() { + let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); + } + + #[test] + fn test_duration_as_seconds_f64() { + assert_eq!(TimeDelta::seconds(1).as_seconds_f64(), 1.0); + assert_eq!(TimeDelta::seconds(-1).as_seconds_f64(), -1.0); + assert_eq!(TimeDelta::seconds(100).as_seconds_f64(), 100.0); + assert_eq!(TimeDelta::seconds(-100).as_seconds_f64(), -100.0); + + assert_eq!(TimeDelta::milliseconds(500).as_seconds_f64(), 0.5); + assert_eq!(TimeDelta::milliseconds(-500).as_seconds_f64(), -0.5); + assert_eq!(TimeDelta::milliseconds(1_500).as_seconds_f64(), 1.5); + assert_eq!(TimeDelta::milliseconds(-1_500).as_seconds_f64(), -1.5); + } + + #[test] + fn test_duration_as_seconds_f32() { + assert_eq!(TimeDelta::seconds(1).as_seconds_f32(), 1.0); + assert_eq!(TimeDelta::seconds(-1).as_seconds_f32(), -1.0); + assert_eq!(TimeDelta::seconds(100).as_seconds_f32(), 100.0); + assert_eq!(TimeDelta::seconds(-100).as_seconds_f32(), -100.0); + + assert_eq!(TimeDelta::milliseconds(500).as_seconds_f32(), 0.5); + assert_eq!(TimeDelta::milliseconds(-500).as_seconds_f32(), -0.5); + assert_eq!(TimeDelta::milliseconds(1_500).as_seconds_f32(), 1.5); + assert_eq!(TimeDelta::milliseconds(-1_500).as_seconds_f32(), -1.5); + } + + #[test] + fn test_duration_subsec_nanos() { + assert_eq!(TimeDelta::zero().subsec_nanos(), 0); + assert_eq!(TimeDelta::nanoseconds(1).subsec_nanos(), 1); + assert_eq!(TimeDelta::nanoseconds(-1).subsec_nanos(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_nanos(), 0); + assert_eq!(TimeDelta::nanoseconds(1_000_000_001).subsec_nanos(), 1); + } + + #[test] + fn test_duration_subsec_micros() { + assert_eq!(TimeDelta::zero().subsec_micros(), 0); + assert_eq!(TimeDelta::microseconds(1).subsec_micros(), 1); + assert_eq!(TimeDelta::microseconds(-1).subsec_micros(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_micros(), 0); + assert_eq!(TimeDelta::microseconds(1_000_001).subsec_micros(), 1); + assert_eq!(TimeDelta::nanoseconds(1_000_001_999).subsec_micros(), 1); + } + + #[test] + fn test_duration_subsec_millis() { + assert_eq!(TimeDelta::zero().subsec_millis(), 0); + assert_eq!(TimeDelta::milliseconds(1).subsec_millis(), 1); + assert_eq!(TimeDelta::milliseconds(-1).subsec_millis(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_millis(), 0); + assert_eq!(TimeDelta::milliseconds(1_001).subsec_millis(), 1); + assert_eq!(TimeDelta::microseconds(1_001_999).subsec_millis(), 1); + } + + #[test] + fn test_duration_num_milliseconds() { + assert_eq!(TimeDelta::zero().num_milliseconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(1).unwrap().num_milliseconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().num_milliseconds(), -1); + assert_eq!(TimeDelta::microseconds(999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(1001).num_milliseconds(), 1); + assert_eq!(TimeDelta::microseconds(-999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(-1001).num_milliseconds(), -1); + } + + #[test] + fn test_duration_milliseconds_max_allowed() { + // The maximum number of milliseconds acceptable through the constructor is + // equal to the number that can be stored in a TimeDelta. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert_eq!(duration.num_milliseconds(), i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_milliseconds_max_overflow() { + // Here we ensure that trying to add one millisecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); + } + + #[test] + fn test_duration_milliseconds_min_allowed() { + // The minimum number of milliseconds acceptable through the constructor is + // not equal to the number that can be stored in a TimeDelta - there is a + // difference of one (i64::MIN vs -i64::MAX). + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert_eq!(duration.num_milliseconds(), -i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_milliseconds_min_underflow() { + // Here we ensure that trying to subtract one millisecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); + } + + #[test] + #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] + fn test_duration_milliseconds_min_underflow_panic() { + // Here we ensure that trying to create a value one millisecond below the + // minimum storable value will fail. This test is necessary because the + // storable range is -i64::MAX, but the constructor type of i64 will allow + // i64::MIN, which is one value below. + let _ = TimeDelta::milliseconds(i64::MIN); // Same as -i64::MAX - 1 + } + + #[test] + fn test_duration_num_microseconds() { + assert_eq!(TimeDelta::zero().num_microseconds(), Some(0)); + assert_eq!(TimeDelta::microseconds(1).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::microseconds(-1).num_microseconds(), Some(-1)); + assert_eq!(TimeDelta::nanoseconds(999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1001).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(-1001).num_microseconds(), Some(-1)); + + // overflow checks + const MICROS_PER_DAY: i64 = 86_400_000_000; + assert_eq!( + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), + Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), + Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY + 1).unwrap().num_microseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY - 1).unwrap().num_microseconds(), + None + ); + } + #[test] + fn test_duration_microseconds_max_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a TimeDelta, so this + // is not a particular insightful test. + let duration = TimeDelta::microseconds(i64::MAX); + assert_eq!(duration.num_microseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000 + ); + // Here we create a TimeDelta with the maximum possible number of + // microseconds by creating a TimeDelta with the maximum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_max_overflow() { + // This test establishes that a TimeDelta can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = TimeDelta::microseconds(i64::MAX) + TimeDelta::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MAX as i128 + 1) * 1_000 + ); + // Here we ensure that trying to add one microsecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::microseconds(1)) + .is_none() + ); + } + #[test] + fn test_duration_microseconds_min_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a TimeDelta, so this + // is not a particular insightful test. + let duration = TimeDelta::microseconds(i64::MIN); + assert_eq!(duration.num_microseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 * 1_000 + ); + // Here we create a TimeDelta with the minimum possible number of + // microseconds by creating a TimeDelta with the minimum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_min_underflow() { + // This test establishes that a TimeDelta can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = TimeDelta::microseconds(i64::MIN) - TimeDelta::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MIN as i128 - 1) * 1_000 + ); + // Here we ensure that trying to subtract one microsecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::microseconds(1)) + .is_none() + ); + } + + #[test] + fn test_duration_num_nanoseconds() { + assert_eq!(TimeDelta::zero().num_nanoseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1).num_nanoseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-1).num_nanoseconds(), Some(-1)); + + // overflow checks + const NANOS_PER_DAY: i64 = 86_400_000_000_000; + assert_eq!( + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), + Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), + Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY + 1).unwrap().num_nanoseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY - 1).unwrap().num_nanoseconds(), + None + ); + } + #[test] + fn test_duration_nanoseconds_max_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a TimeDelta, so this is not + // a particular insightful test. + let duration = TimeDelta::nanoseconds(i64::MAX); + assert_eq!(duration.num_nanoseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + ); + // Here we create a TimeDelta with the maximum possible number of nanoseconds + // by creating a TimeDelta with the maximum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_nanoseconds_max_overflow() { + // This test establishes that a TimeDelta can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = TimeDelta::nanoseconds(i64::MAX) + TimeDelta::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + 1 + ); + // Here we ensure that trying to add one nanosecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::nanoseconds(1)) + .is_none() + ); + } + + #[test] + fn test_duration_nanoseconds_min_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a TimeDelta, so this is not + // a particular insightful test. + let duration = TimeDelta::nanoseconds(i64::MIN); + assert_eq!(duration.num_nanoseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 + ); + // Here we create a TimeDelta with the minimum possible number of nanoseconds + // by creating a TimeDelta with the minimum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_nanoseconds_min_underflow() { + // This test establishes that a TimeDelta can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = TimeDelta::nanoseconds(i64::MIN) - TimeDelta::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 - 1 + ); + // Here we ensure that trying to subtract one nanosecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::nanoseconds(1)) + .is_none() + ); + } + + #[test] + fn test_max() { + assert_eq!( + MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MAX, TimeDelta::try_milliseconds(i64::MAX).unwrap()); + assert_eq!(MAX.num_milliseconds(), i64::MAX); + assert_eq!(MAX.num_microseconds(), None); + assert_eq!(MAX.num_nanoseconds(), None); + } + + #[test] + fn test_min() { + assert_eq!( + MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MIN, TimeDelta::try_milliseconds(-i64::MAX).unwrap()); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); + assert_eq!(MIN.num_microseconds(), None); + assert_eq!(MIN.num_nanoseconds(), None); + } + + #[test] + fn test_duration_ord() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert!(milliseconds(1) < milliseconds(2)); + assert!(milliseconds(2) > milliseconds(1)); + assert!(milliseconds(-1) > milliseconds(-2)); + assert!(milliseconds(-2) < milliseconds(-1)); + assert!(milliseconds(-1) < milliseconds(1)); + assert!(milliseconds(1) > milliseconds(-1)); + assert!(milliseconds(0) < milliseconds(1)); + assert!(milliseconds(0) > milliseconds(-1)); + assert!(milliseconds(1_001) < milliseconds(1_002)); + assert!(milliseconds(-1_001) > milliseconds(-1_002)); + assert!(TimeDelta::nanoseconds(1_234_567_890) < TimeDelta::nanoseconds(1_234_567_891)); + assert!(TimeDelta::nanoseconds(-1_234_567_890) > TimeDelta::nanoseconds(-1_234_567_891)); + assert!(milliseconds(i64::MAX) > milliseconds(i64::MAX - 1)); + assert!(milliseconds(-i64::MAX) < milliseconds(-i64::MAX + 1)); + } + + #[test] + fn test_duration_checked_ops() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + assert_eq!( + milliseconds(i64::MAX).checked_add(&milliseconds(0)), + Some(milliseconds(i64::MAX)) + ); + assert_eq!( + milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)), + Some(milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999)) + ); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::microseconds(1000)).is_none()); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::nanoseconds(1)).is_none()); + + assert_eq!( + milliseconds(-i64::MAX).checked_sub(&milliseconds(0)), + Some(milliseconds(-i64::MAX)) + ); + assert_eq!( + milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)), + Some(milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999)) + ); + assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none()); + assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none()); + + assert!(seconds(i64::MAX / 1000).checked_mul(2000).is_none()); + assert!(seconds(i64::MIN / 1000).checked_mul(2000).is_none()); + assert!(seconds(1).checked_div(0).is_none()); + } + + #[test] + fn test_duration_abs() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert_eq!(milliseconds(1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(300).abs(), milliseconds(300)); + assert_eq!(milliseconds(0).abs(), milliseconds(0)); + assert_eq!(milliseconds(-300).abs(), milliseconds(300)); + assert_eq!(milliseconds(-700).abs(), milliseconds(700)); + assert_eq!(milliseconds(-1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(-1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(-1700).abs(), milliseconds(1700)); + assert_eq!(milliseconds(-i64::MAX).abs(), milliseconds(i64::MAX)); + } + + #[test] + #[allow(clippy::erasing_op)] + fn test_duration_mul() { + assert_eq!(TimeDelta::zero() * i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() * i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 0, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 1, TimeDelta::nanoseconds(1)); + assert_eq!(TimeDelta::nanoseconds(1) * 1_000_000_000, TimeDelta::try_seconds(1).unwrap()); + assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); + assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); + assert_eq!( + TimeDelta::nanoseconds(30) * 333_333_333, + TimeDelta::try_seconds(10).unwrap() - TimeDelta::nanoseconds(10) + ); + assert_eq!( + (TimeDelta::nanoseconds(1) + + TimeDelta::try_seconds(1).unwrap() + + TimeDelta::try_days(1).unwrap()) + * 3, + TimeDelta::nanoseconds(3) + + TimeDelta::try_seconds(3).unwrap() + + TimeDelta::try_days(3).unwrap() + ); + assert_eq!( + TimeDelta::try_milliseconds(1500).unwrap() * -2, + TimeDelta::try_seconds(-3).unwrap() + ); + assert_eq!( + TimeDelta::try_milliseconds(-1500).unwrap() * 2, + TimeDelta::try_seconds(-3).unwrap() + ); + } + + #[test] + fn test_duration_div() { + assert_eq!(TimeDelta::zero() / i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() / i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / 1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / -1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / -1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / 1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::try_seconds(1).unwrap() / 3, TimeDelta::nanoseconds(333_333_333)); + assert_eq!(TimeDelta::try_seconds(4).unwrap() / 3, TimeDelta::nanoseconds(1_333_333_333)); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / 2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(1).unwrap() / -2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / -2, + TimeDelta::try_milliseconds(500).unwrap() + ); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / 3, TimeDelta::nanoseconds(-1_333_333_333)); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / -3, TimeDelta::nanoseconds(1_333_333_333)); + } + + #[test] + fn test_duration_sum() { + let duration_list_1 = [TimeDelta::zero(), TimeDelta::try_seconds(1).unwrap()]; + let sum_1: TimeDelta = duration_list_1.iter().sum(); + assert_eq!(sum_1, TimeDelta::try_seconds(1).unwrap()); + + let duration_list_2 = [ + TimeDelta::zero(), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), + ]; + let sum_2: TimeDelta = duration_list_2.iter().sum(); + assert_eq!(sum_2, TimeDelta::try_seconds(17).unwrap()); + + let duration_arr = [ + TimeDelta::zero(), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), + ]; + let sum_3: TimeDelta = duration_arr.into_iter().sum(); + assert_eq!(sum_3, TimeDelta::try_seconds(17).unwrap()); + } + + #[test] + fn test_duration_fmt() { + assert_eq!(TimeDelta::zero().to_string(), "P0D"); + assert_eq!(TimeDelta::try_days(42).unwrap().to_string(), "PT3628800S"); + assert_eq!(TimeDelta::try_days(-42).unwrap().to_string(), "-PT3628800S"); + assert_eq!(TimeDelta::try_seconds(42).unwrap().to_string(), "PT42S"); + assert_eq!(TimeDelta::try_milliseconds(42).unwrap().to_string(), "PT0.042S"); + assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); + assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); + assert_eq!( + (TimeDelta::try_days(7).unwrap() + TimeDelta::try_milliseconds(6543).unwrap()) + .to_string(), + "PT604806.543S" + ); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().to_string(), "-PT86401S"); + assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S"); + + // the format specifier should have no effect on `TimeDelta` + assert_eq!( + format!( + "{:30}", + TimeDelta::try_days(1).unwrap() + TimeDelta::try_milliseconds(2345).unwrap() + ), + "PT86402.345S" + ); + } + + #[test] + fn test_to_std() { + assert_eq!(TimeDelta::try_seconds(1).unwrap().to_std(), Ok(Duration::new(1, 0))); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().to_std(), Ok(Duration::new(86_401, 0))); + assert_eq!( + TimeDelta::try_milliseconds(123).unwrap().to_std(), + Ok(Duration::new(0, 123_000_000)) + ); + assert_eq!( + TimeDelta::try_milliseconds(123_765).unwrap().to_std(), + Ok(Duration::new(123, 765_000_000)) + ); + assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(Duration::new(0, 777))); + assert_eq!(MAX.to_std(), Ok(Duration::new(9_223_372_036_854_775, 807_000_000))); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); + } + + #[test] + fn test_from_std() { + assert_eq!( + Ok(TimeDelta::try_seconds(1).unwrap()), + TimeDelta::from_std(Duration::new(1, 0)) + ); + assert_eq!( + Ok(TimeDelta::try_seconds(86_401).unwrap()), + TimeDelta::from_std(Duration::new(86_401, 0)) + ); + assert_eq!( + Ok(TimeDelta::try_milliseconds(123).unwrap()), + TimeDelta::from_std(Duration::new(0, 123_000_000)) + ); + assert_eq!( + Ok(TimeDelta::try_milliseconds(123_765).unwrap()), + TimeDelta::from_std(Duration::new(123, 765_000_000)) + ); + assert_eq!(Ok(TimeDelta::nanoseconds(777)), TimeDelta::from_std(Duration::new(0, 777))); + assert_eq!(Ok(MAX), TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_000))); + assert_eq!( + TimeDelta::from_std(Duration::new(9_223_372_036_854_776, 0)), + Err(OutOfRangeError(())) + ); + assert_eq!( + TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_001)), + Err(OutOfRangeError(())) + ); + } + + #[test] + fn test_duration_const() { + const ONE_WEEK: TimeDelta = expect(TimeDelta::try_weeks(1), ""); + const ONE_DAY: TimeDelta = expect(TimeDelta::try_days(1), ""); + const ONE_HOUR: TimeDelta = expect(TimeDelta::try_hours(1), ""); + const ONE_MINUTE: TimeDelta = expect(TimeDelta::try_minutes(1), ""); + const ONE_SECOND: TimeDelta = expect(TimeDelta::try_seconds(1), ""); + const ONE_MILLI: TimeDelta = expect(TimeDelta::try_milliseconds(1), ""); + const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); + const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); + let combo: TimeDelta = ONE_WEEK + + ONE_DAY + + ONE_HOUR + + ONE_MINUTE + + ONE_SECOND + + ONE_MILLI + + ONE_MICRO + + ONE_NANO; + + assert!(ONE_WEEK != TimeDelta::zero()); + assert!(ONE_DAY != TimeDelta::zero()); + assert!(ONE_HOUR != TimeDelta::zero()); + assert!(ONE_MINUTE != TimeDelta::zero()); + assert!(ONE_SECOND != TimeDelta::zero()); + assert!(ONE_MILLI != TimeDelta::zero()); + assert!(ONE_MICRO != TimeDelta::zero()); + assert!(ONE_NANO != TimeDelta::zero()); + assert_eq!( + combo, + TimeDelta::try_seconds(86400 * 7 + 86400 + 3600 + 60 + 1).unwrap() + + TimeDelta::nanoseconds(1 + 1_000 + 1_000_000) + ); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let duration = TimeDelta::try_seconds(1).unwrap(); + let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); + } +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000000..450f0a2259 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,409 @@ +use crate::{IsoWeek, Month, Weekday}; + +/// The common set of methods for date component. +/// +/// Methods such as [`year`], [`month`], [`day`] and [`weekday`] can be used to get basic +/// information about the date. +/// +/// The `with_*` methods can change the date. +/// +/// # Warning +/// +/// The `with_*` methods can be convenient to change a single component of a date, but they must be +/// used with some care. Examples to watch out for: +/// +/// - [`with_year`] changes the year component of a year-month-day value. Don't use this method if +/// you want the ordinal to stay the same after changing the year, of if you want the week and +/// weekday values to stay the same. +/// - Don't combine two `with_*` methods to change two components of the date. For example to +/// change both the year and month components of a date. This could fail because an intermediate +/// value does not exist, while the final date would be valid. +/// +/// For more complex changes to a date, it is best to use the methods on [`NaiveDate`] to create a +/// new value instead of altering an existing date. +/// +/// [`year`]: Datelike::year +/// [`month`]: Datelike::month +/// [`day`]: Datelike::day +/// [`weekday`]: Datelike::weekday +/// [`with_year`]: Datelike::with_year +/// [`NaiveDate`]: crate::NaiveDate +pub trait Datelike: Sized { + /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). + fn year(&self) -> i32; + + /// Returns the absolute year number starting from 1 with a boolean flag, + /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). + #[inline] + fn year_ce(&self) -> (bool, u32) { + let year = self.year(); + if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) } + } + + /// Returns the quarter number starting from 1. + /// + /// The return value ranges from 1 to 4. + #[inline] + fn quarter(&self) -> u32 { + (self.month() - 1).div_euclid(3) + 1 + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + fn month(&self) -> u32; + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + fn month0(&self) -> u32; + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + fn day(&self) -> u32; + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + fn day0(&self) -> u32; + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + fn ordinal(&self) -> u32; + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + fn ordinal0(&self) -> u32; + + /// Returns the day of week. + fn weekday(&self) -> Weekday; + + /// Returns the ISO week. + fn iso_week(&self) -> IsoWeek; + + /// Makes a new value with the year number changed, while keeping the same month and day. + /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for [`NaiveDate`]. + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// + /// [`NaiveDate`]: crate::NaiveDate + /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 5, 13).unwrap() + /// ); + /// // Resulting date 2023-02-29 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().with_year(2023).is_none()); + /// + /// // Don't use `with_year` if you want the ordinal date to stay the same: + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` + fn with_year(&self, year: i32) -> Option; + + /// Makes a new value with the month number (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month` is out of range. + /// + /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 9, 12).unwrap() + /// ); + /// // Resulting date 2023-09-31 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2023, 5, 31).unwrap().with_month(9).is_none()); + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); + /// ``` + fn with_month(&self, month: u32) -> Option; + + /// Makes a new value with the month number (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_month0(&self, month0: u32) -> Option; + + /// Makes a new value with the day of month (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day(31)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_day(&self, day: u32) -> Option; + + /// Makes a new value with the day of month (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day0(30)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_day0(&self, day0: u32) -> Option; + + /// Makes a new value with the day of year (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_ordinal(&self, ordinal: u32) -> Option; + + /// Makes a new value with the day of year (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_ordinal0(&self, ordinal0: u32) -> Option; + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163); + /// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366); + /// assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + /// assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), -365); + /// ``` + fn num_days_from_ce(&self) -> i32 { + // See test_num_days_from_ce_against_alternative_impl below for a more straightforward + // implementation. + + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } + + /// Get the length in days of the month + fn num_days_in_month(&self) -> u8 { + use num_traits::FromPrimitive; + // The value returned from `self.month()` is guaranteed to be in the + // range [1,12], which will never result in a `None` value here. + let month = Month::from_u32(self.month()).unwrap(); + // `Month::num_days` will only return `None` if the provided year is out + // of range. Since we are passing it directly from a verified date, we + // know it is in range, and the result will never be `None`. + month.num_days(self.year()).unwrap() + } +} + +/// The common set of methods for time component. +pub trait Timelike: Sized { + /// Returns the hour number from 0 to 23. + fn hour(&self) -> u32; + + /// Returns the hour number from 1 to 12 with a boolean flag, + /// which is false for AM and true for PM. + #[inline] + fn hour12(&self) -> (bool, u32) { + let hour = self.hour(); + let mut hour12 = hour % 12; + if hour12 == 0 { + hour12 = 12; + } + (hour >= 12, hour12) + } + + /// Returns the minute number from 0 to 59. + fn minute(&self) -> u32; + + /// Returns the second number from 0 to 59. + fn second(&self) -> u32; + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). + fn nanosecond(&self) -> u32; + + /// Makes a new value with the hour number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_hour(&self, hour: u32) -> Option; + + /// Makes a new value with the minute number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_minute(&self, min: u32) -> Option; + + /// Makes a new value with the second number changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`second`](#tymethod.second) method, + /// the input range is restricted to 0 through 59. + fn with_second(&self, sec: u32) -> Option; + + /// Makes a new value with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`nanosecond`](#tymethod.nanosecond) method, + /// the input range can exceed 1,000,000,000 for leap seconds. + fn with_nanosecond(&self, nano: u32) -> Option; + + /// Returns the number of non-leap seconds past the last midnight. + /// + /// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399. + /// + /// This method is not intended to provide the real number of seconds since midnight on a given + /// day. It does not take things like DST transitions into account. + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { + self.hour() * 3600 + self.minute() * 60 + self.second() + } +} + +#[cfg(test)] +mod tests { + use super::Datelike; + use crate::{Days, NaiveDate}; + + /// Tests `Datelike::num_days_from_ce` against an alternative implementation. + /// + /// The alternative implementation is not as short as the current one but it is simpler to + /// understand, with less unexplained magic constants. + #[test] + fn test_num_days_from_ce_against_alternative_impl() { + /// Returns the number of multiples of `div` in the range `start..end`. + /// + /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the + /// behaviour is defined by the following equation: + /// `in_between(start, end, div) == - in_between(end, start, div)`. + /// + /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. + /// + /// # Panics + /// + /// Panics if `div` is not positive. + fn in_between(start: i32, end: i32, div: i32) -> i32 { + assert!(div > 0, "in_between: nonpositive div = {div}"); + let start = (start.div_euclid(div), start.rem_euclid(div)); + let end = (end.div_euclid(div), end.rem_euclid(div)); + // The lowest multiple of `div` greater than or equal to `start`, divided. + let start = start.0 + (start.1 != 0) as i32; + // The lowest multiple of `div` greater than or equal to `end`, divided. + let end = end.0 + (end.1 != 0) as i32; + end - start + } + + /// Alternative implementation to `Datelike::num_days_from_ce` + fn num_days_from_ce(date: &Date) -> i32 { + let year = date.year(); + let diff = move |div| in_between(1, year, div); + // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all + // the multiples of 4 except multiples of 100 but including multiples of 400. + date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) + } + + for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { + let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + assert_eq!( + jan1_year.num_days_from_ce(), + num_days_from_ce(&jan1_year), + "on {jan1_year:?}" + ); + let mid_year = jan1_year + Days::new(133); + assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {mid_year:?}"); + } + } + + #[test] + fn test_num_days_in_month() { + let feb_leap_year = NaiveDate::from_ymd_opt(2004, 2, 1).unwrap(); + assert_eq!(feb_leap_year.num_days_in_month(), 29); + let feb = feb_leap_year.with_year(2005).unwrap(); + assert_eq!(feb.num_days_in_month(), 28); + let march = feb.with_month(3).unwrap(); + assert_eq!(march.num_days_in_month(), 31); + } +} diff --git a/src/weekday.rs b/src/weekday.rs new file mode 100644 index 0000000000..da4746b8ae --- /dev/null +++ b/src/weekday.rs @@ -0,0 +1,419 @@ +use core::fmt; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::OutOfRange; + +/// The day of week. +/// +/// The order of the days of week depends on the context. +/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) +/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. +/// +/// # Example +/// ``` +/// use chrono::Weekday; +/// +/// let monday = "Monday".parse::().unwrap(); +/// assert_eq!(monday, Weekday::Mon); +/// +/// let sunday = Weekday::try_from(6).unwrap(); +/// assert_eq!(sunday, Weekday::Sun); +/// +/// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 +/// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1 +/// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0 +/// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1 +/// +/// assert_eq!(sunday.succ(), monday); +/// assert_eq!(sunday.pred(), Weekday::Sat); +/// ``` +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Weekday { + /// Monday. + Mon = 0, + /// Tuesday. + Tue = 1, + /// Wednesday. + Wed = 2, + /// Thursday. + Thu = 3, + /// Friday. + Fri = 4, + /// Saturday. + Sat = 5, + /// Sunday. + Sun = 6, +} + +impl Weekday { + /// The next day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` + #[inline] + #[must_use] + pub const fn succ(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Tue, + Weekday::Tue => Weekday::Wed, + Weekday::Wed => Weekday::Thu, + Weekday::Thu => Weekday::Fri, + Weekday::Fri => Weekday::Sat, + Weekday::Sat => Weekday::Sun, + Weekday::Sun => Weekday::Mon, + } + } + + /// The previous day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` + #[inline] + #[must_use] + pub const fn pred(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Sun, + Weekday::Tue => Weekday::Mon, + Weekday::Wed => Weekday::Tue, + Weekday::Thu => Weekday::Wed, + Weekday::Fri => Weekday::Thu, + Weekday::Sat => Weekday::Fri, + Weekday::Sun => Weekday::Sat, + } + } + + /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 + #[inline] + pub const fn number_from_monday(&self) -> u32 { + self.days_since(Weekday::Mon) + 1 + } + + /// Returns a day-of-week number starting from Sunday = 1. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 + #[inline] + pub const fn number_from_sunday(&self) -> u32 { + self.days_since(Weekday::Sun) + 1 + } + + /// Returns a day-of-week number starting from Monday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// # use chrono::{Local, Datelike}; + /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays. + /// // Use `num_days_from_monday` to index into the array. + /// const MTWRFSU: [char; 7] = ['M', 'T', 'W', 'R', 'F', 'S', 'U']; + /// + /// let today = Local::now().weekday(); + /// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]); + /// # } + /// ``` + #[inline] + pub const fn num_days_from_monday(&self) -> u32 { + self.days_since(Weekday::Mon) + } + + /// Returns a day-of-week number starting from Sunday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 + #[inline] + pub const fn num_days_from_sunday(&self) -> u32 { + self.days_since(Weekday::Sun) + } + + /// The number of days since the given day. + /// + /// # Examples + /// + /// ``` + /// use chrono::Weekday::*; + /// assert_eq!(Mon.days_since(Mon), 0); + /// assert_eq!(Sun.days_since(Tue), 5); + /// assert_eq!(Wed.days_since(Sun), 3); + /// ``` + pub const fn days_since(&self, other: Weekday) -> u32 { + let lhs = *self as u32; + let rhs = other as u32; + if lhs < rhs { 7 + lhs - rhs } else { lhs - rhs } + } +} + +impl fmt::Display for Weekday { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(match *self { + Weekday::Mon => "Mon", + Weekday::Tue => "Tue", + Weekday::Wed => "Wed", + Weekday::Thu => "Thu", + Weekday::Fri => "Fri", + Weekday::Sat => "Sat", + Weekday::Sun => "Sun", + }) + } +} + +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl TryFrom for Weekday { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Weekday::Mon), + 1 => Ok(Weekday::Tue), + 2 => Ok(Weekday::Wed), + 3 => Ok(Weekday::Thu), + 4 => Ok(Weekday::Fri), + 5 => Ok(Weekday::Sat), + 6 => Ok(Weekday::Sun), + _ => Err(OutOfRange::new()), + } + } +} + +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl num_traits::FromPrimitive for Weekday { + #[inline] + fn from_i64(n: i64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } + + #[inline] + fn from_u64(n: u64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } +} + +/// An error resulting from reading `Weekday` value with `FromStr`. +#[derive(Clone, PartialEq, Eq)] +pub struct ParseWeekdayError { + pub(crate) _dummy: (), +} + +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for ParseWeekdayError {} + +#[cfg(feature = "std")] +impl std::error::Error for ParseWeekdayError {} + +impl fmt::Display for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("{self:?}")) + } +} + +impl fmt::Debug for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseWeekdayError {{ .. }}") + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ParseWeekdayError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "ParseWeekdayError {{ .. }}") + } +} + +// the actual `FromStr` implementation is in the `format` module to leverage the existing code + +#[cfg(feature = "serde")] +mod weekday_serde { + use super::Weekday; + use core::fmt; + use serde::{de, ser}; + + impl ser::Serialize for Weekday { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } + } + + struct WeekdayVisitor; + + impl de::Visitor<'_> for WeekdayVisitor { + type Value = Weekday; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Weekday") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short or long weekday names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Weekday { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(WeekdayVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::Weekday; + + #[test] + fn test_days_since() { + for i in 0..7 { + let base_day = Weekday::try_from(i).unwrap(); + + assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun)); + + assert_eq!(base_day.days_since(base_day), 0); + + assert_eq!(base_day.days_since(base_day.pred()), 1); + assert_eq!(base_day.days_since(base_day.pred().pred()), 2); + assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6); + + assert_eq!(base_day.days_since(base_day.succ()), 6); + assert_eq!(base_day.days_since(base_day.succ().succ()), 5); + assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1); + } + } + + #[test] + fn test_formatting_alignment() { + // No exhaustive testing here as we just delegate the + // implementation to Formatter::pad. Just some basic smoke + // testing to ensure that it's in fact being done. + assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon"); + assert_eq!(format!("{:^7}", Weekday::Mon), " Mon "); + assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ"); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialize() { + use Weekday::*; + use serde_json::to_string; + + let cases: Vec<(Weekday, &str)> = vec![ + (Mon, "\"Mon\""), + (Tue, "\"Tue\""), + (Wed, "\"Wed\""), + (Thu, "\"Thu\""), + (Fri, "\"Fri\""), + (Sat, "\"Sat\""), + (Sun, "\"Sun\""), + ]; + + for (weekday, expected_str) in cases { + let string = to_string(&weekday).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialize() { + use Weekday::*; + use serde_json::from_str; + + let cases: Vec<(&str, Weekday)> = vec![ + ("\"mon\"", Mon), + ("\"MONDAY\"", Mon), + ("\"MonDay\"", Mon), + ("\"mOn\"", Mon), + ("\"tue\"", Tue), + ("\"tuesday\"", Tue), + ("\"wed\"", Wed), + ("\"wednesday\"", Wed), + ("\"thu\"", Thu), + ("\"thursday\"", Thu), + ("\"fri\"", Fri), + ("\"friday\"", Fri), + ("\"sat\"", Sat), + ("\"saturday\"", Sat), + ("\"sun\"", Sun), + ("\"sunday\"", Sun), + ]; + + for (str, expected_weekday) in cases { + let weekday = from_str::(str).unwrap(); + assert_eq!(weekday, expected_weekday); + } + + let errors: Vec<&str> = + vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; + + for str in errors { + from_str::(str).unwrap_err(); + } + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let mon = Weekday::Mon; + let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); + + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), mon); + } +} diff --git a/src/weekday_set.rs b/src/weekday_set.rs new file mode 100644 index 0000000000..989131c209 --- /dev/null +++ b/src/weekday_set.rs @@ -0,0 +1,500 @@ +use core::{ + fmt::{self, Debug}, + iter::FusedIterator, +}; + +use crate::Weekday; + +/// A collection of [`Weekday`]s stored as a single byte. +/// +/// This type is `Copy` and provides efficient set-like and slice-like operations. +/// Many operations are `const` as well. +/// +/// Implemented as a bitmask where bits 1-7 correspond to Monday-Sunday. +#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0. + +impl WeekdaySet { + /// Create a `WeekdaySet` from an array of [`Weekday`]s. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::EMPTY, WeekdaySet::from_array([])); + /// assert_eq!(WeekdaySet::single(Mon), WeekdaySet::from_array([Mon])); + /// assert_eq!(WeekdaySet::ALL, WeekdaySet::from_array([Mon, Tue, Wed, Thu, Fri, Sat, Sun])); + /// ``` + pub const fn from_array(days: [Weekday; C]) -> Self { + let mut acc = Self::EMPTY; + let mut idx = 0; + while idx < days.len() { + acc.0 |= Self::single(days[idx]).0; + idx += 1; + } + acc + } + + /// Create a `WeekdaySet` from a single [`Weekday`]. + pub const fn single(weekday: Weekday) -> Self { + match weekday { + Weekday::Mon => Self(0b000_0001), + Weekday::Tue => Self(0b000_0010), + Weekday::Wed => Self(0b000_0100), + Weekday::Thu => Self(0b000_1000), + Weekday::Fri => Self(0b001_0000), + Weekday::Sat => Self(0b010_0000), + Weekday::Sun => Self(0b100_0000), + } + } + + /// Returns `Some(day)` if this collection contains exactly one day. + /// + /// Returns `None` otherwise. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).single_day(), Some(Mon)); + /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).single_day(), None); + /// assert_eq!(WeekdaySet::EMPTY.single_day(), None); + /// assert_eq!(WeekdaySet::ALL.single_day(), None); + /// ``` + pub const fn single_day(self) -> Option { + match self { + Self(0b000_0001) => Some(Weekday::Mon), + Self(0b000_0010) => Some(Weekday::Tue), + Self(0b000_0100) => Some(Weekday::Wed), + Self(0b000_1000) => Some(Weekday::Thu), + Self(0b001_0000) => Some(Weekday::Fri), + Self(0b010_0000) => Some(Weekday::Sat), + Self(0b100_0000) => Some(Weekday::Sun), + _ => None, + } + } + + /// Adds a day to the collection. + /// + /// Returns `true` if the day was new to the collection. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let mut weekdays = WeekdaySet::single(Mon); + /// assert!(weekdays.insert(Tue)); + /// assert!(!weekdays.insert(Tue)); + /// ``` + pub fn insert(&mut self, day: Weekday) -> bool { + if self.contains(day) { + return false; + } + + self.0 |= Self::single(day).0; + true + } + + /// Removes a day from the collection. + /// + /// Returns `true` if the collection did contain the day. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let mut weekdays = WeekdaySet::single(Mon); + /// assert!(weekdays.remove(Mon)); + /// assert!(!weekdays.remove(Mon)); + /// ``` + pub fn remove(&mut self, day: Weekday) -> bool { + if self.contains(day) { + self.0 &= !Self::single(day).0; + return true; + } + + false + } + + /// Returns `true` if `other` contains all days in `self`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert!(WeekdaySet::single(Mon).is_subset(WeekdaySet::ALL)); + /// assert!(!WeekdaySet::single(Mon).is_subset(WeekdaySet::EMPTY)); + /// assert!(WeekdaySet::EMPTY.is_subset(WeekdaySet::single(Mon))); + /// ``` + pub const fn is_subset(self, other: Self) -> bool { + self.intersection(other).0 == self.0 + } + + /// Returns days that are in both `self` and `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Tue)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::EMPTY), WeekdaySet::EMPTY); + /// ``` + pub const fn intersection(self, other: Self) -> Self { + Self(self.0 & other.0) + } + + /// Returns days that are in either `self` or `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::single(Mon)), WeekdaySet::ALL); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Returns days that are in `self` or `other` but not in both. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!( + /// WeekdaySet::ALL.symmetric_difference(WeekdaySet::single(Mon)), + /// WeekdaySet::from_array([Tue, Wed, Thu, Fri, Sat, Sun]), + /// ); + /// assert_eq!(WeekdaySet::ALL.symmetric_difference(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn symmetric_difference(self, other: Self) -> Self { + Self(self.0 ^ other.0) + } + + /// Returns days that are in `self` but not in `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Tue)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::EMPTY.difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// ``` + pub const fn difference(self, other: Self) -> Self { + Self(self.0 & !other.0) + } + + /// Get the first day in the collection, starting from Monday. + /// + /// Returns `None` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).first(), Some(Mon)); + /// assert_eq!(WeekdaySet::single(Tue).first(), Some(Tue)); + /// assert_eq!(WeekdaySet::ALL.first(), Some(Mon)); + /// assert_eq!(WeekdaySet::EMPTY.first(), None); + /// ``` + pub const fn first(self) -> Option { + if self.is_empty() { + return None; + } + + // Find the first non-zero bit. + let bit = 1 << self.0.trailing_zeros(); + + Self(bit).single_day() + } + + /// Get the last day in the collection, starting from Sunday. + /// + /// Returns `None` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).last(), Some(Mon)); + /// assert_eq!(WeekdaySet::single(Sun).last(), Some(Sun)); + /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).last(), Some(Tue)); + /// assert_eq!(WeekdaySet::EMPTY.last(), None); + /// ``` + pub fn last(self) -> Option { + if self.is_empty() { + return None; + } + + // Find the last non-zero bit. + let bit = 1 << (7 - self.0.leading_zeros()); + + Self(bit).single_day() + } + + /// Split the collection in two at the given day. + /// + /// Returns a tuple `(before, after)`. `before` contains all days starting from Monday + /// up to but __not__ including `weekday`. `after` contains all days starting from `weekday` + /// up to and including Sunday. + const fn split_at(self, weekday: Weekday) -> (Self, Self) { + let days_after = 0b1000_0000 - Self::single(weekday).0; + let days_before = days_after ^ 0b0111_1111; + (Self(self.0 & days_before), Self(self.0 & days_after)) + } + + /// Iterate over the [`Weekday`]s in the collection starting from a given day. + /// + /// Wraps around from Sunday to Monday if necessary. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let weekdays = WeekdaySet::from_array([Mon, Wed, Fri]); + /// let mut iter = weekdays.iter(Wed); + /// assert_eq!(iter.next(), Some(Wed)); + /// assert_eq!(iter.next(), Some(Fri)); + /// assert_eq!(iter.next(), Some(Mon)); + /// assert_eq!(iter.next(), None); + /// ``` + pub const fn iter(self, start: Weekday) -> WeekdaySetIter { + WeekdaySetIter { days: self, start } + } + + /// Returns `true` if the collection contains the given day. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert!(WeekdaySet::single(Mon).contains(Mon)); + /// assert!(WeekdaySet::from_array([Mon, Tue]).contains(Tue)); + /// assert!(!WeekdaySet::single(Mon).contains(Tue)); + /// ``` + pub const fn contains(self, day: Weekday) -> bool { + self.0 & Self::single(day).0 != 0 + } + + /// Returns `true` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::{Weekday, WeekdaySet}; + /// assert!(WeekdaySet::EMPTY.is_empty()); + /// assert!(!WeekdaySet::single(Weekday::Mon).is_empty()); + /// ``` + pub const fn is_empty(self) -> bool { + self.len() == 0 + } + /// Returns the number of days in the collection. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).len(), 1); + /// assert_eq!(WeekdaySet::from_array([Mon, Wed, Fri]).len(), 3); + /// assert_eq!(WeekdaySet::ALL.len(), 7); + /// ``` + pub const fn len(self) -> u8 { + self.0.count_ones() as u8 + } + + /// An empty `WeekdaySet`. + pub const EMPTY: Self = Self(0b000_0000); + /// A `WeekdaySet` containing all seven `Weekday`s. + pub const ALL: Self = Self(0b111_1111); +} + +/// Print the underlying bitmask, padded to 7 bits. +/// +/// # Example +/// ``` +/// # use chrono::WeekdaySet; +/// use chrono::Weekday::*; +/// assert_eq!(format!("{:?}", WeekdaySet::single(Mon)), "WeekdaySet(0000001)"); +/// assert_eq!(format!("{:?}", WeekdaySet::single(Tue)), "WeekdaySet(0000010)"); +/// assert_eq!(format!("{:?}", WeekdaySet::ALL), "WeekdaySet(1111111)"); +/// ``` +impl Debug for WeekdaySet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WeekdaySet({:0>7b})", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for WeekdaySet { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!( + f, + "WeekdaySet({}{}{}{}{}{}{})", + 0x1 & (self.0 >> 6), + 0x1 & (self.0 >> 5), + 0x1 & (self.0 >> 4), + 0x1 & (self.0 >> 3), + 0x1 & (self.0 >> 2), + 0x1 & (self.0 >> 1), + 0x1 & (self.0 >> 0), + ) + } +} + +/// An iterator over a collection of weekdays, starting from a given day. +/// +/// See [`WeekdaySet::iter()`]. +#[derive(Debug, Clone)] +pub struct WeekdaySetIter { + days: WeekdaySet, + start: Weekday, +} + +impl Iterator for WeekdaySetIter { + type Item = Weekday; + + fn next(&mut self) -> Option { + if self.days.is_empty() { + return None; + } + + // Split the collection in two at `start`. + // Look for the first day among the days after `start` first, including `start` itself. + // If there are no days after `start`, look for the first day among the days before `start`. + let (before, after) = self.days.split_at(self.start); + let days = if after.is_empty() { before } else { after }; + + let next = days.first().expect("the collection is not empty"); + self.days.remove(next); + Some(next) + } +} + +impl DoubleEndedIterator for WeekdaySetIter { + fn next_back(&mut self) -> Option { + if self.days.is_empty() { + return None; + } + + // Split the collection in two at `start`. + // Look for the last day among the days before `start` first, NOT including `start` itself. + // If there are no days before `start`, look for the last day among the days after `start`. + let (before, after) = self.days.split_at(self.start); + let days = if before.is_empty() { after } else { before }; + + let next_back = days.last().expect("the collection is not empty"); + self.days.remove(next_back); + Some(next_back) + } +} + +impl ExactSizeIterator for WeekdaySetIter { + fn len(&self) -> usize { + self.days.len().into() + } +} + +impl FusedIterator for WeekdaySetIter {} + +/// Print the collection as a slice-like list of weekdays. +/// +/// # Example +/// ``` +/// # use chrono::WeekdaySet; +/// use chrono::Weekday::*; +/// assert_eq!("[]", WeekdaySet::EMPTY.to_string()); +/// assert_eq!("[Mon]", WeekdaySet::single(Mon).to_string()); +/// assert_eq!("[Mon, Fri, Sun]", WeekdaySet::from_array([Mon, Fri, Sun]).to_string()); +/// ``` +impl fmt::Display for WeekdaySet { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[")?; + let mut iter = self.iter(Weekday::Mon); + if let Some(first) = iter.next() { + write!(f, "{first}")?; + } + for weekday in iter { + write!(f, ", {weekday}")?; + } + write!(f, "]") + } +} + +impl FromIterator for WeekdaySet { + fn from_iter>(iter: T) -> Self { + iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union) + } +} + +#[cfg(test)] +mod tests { + use crate::Weekday; + + use super::WeekdaySet; + + impl WeekdaySet { + /// Iterate over all 128 possible sets, from `EMPTY` to `ALL`. + fn iter_all() -> impl Iterator { + (0b0000_0000..0b1000_0000).map(Self) + } + } + + /// Panics if the 8-th bit of `self` is not 0. + fn assert_8th_bit_invariant(days: WeekdaySet) { + assert!(days.0 & 0b1000_0000 == 0, "the 8-th bit of {days:?} is not 0"); + } + + #[test] + fn debug_prints_8th_bit_if_not_zero() { + assert_eq!(format!("{:?}", WeekdaySet(0b1000_0000)), "WeekdaySet(10000000)"); + } + + #[test] + fn bitwise_set_operations_preserve_8th_bit_invariant() { + for set1 in WeekdaySet::iter_all() { + for set2 in WeekdaySet::iter_all() { + assert_8th_bit_invariant(set1.union(set2)); + assert_8th_bit_invariant(set1.intersection(set2)); + assert_8th_bit_invariant(set1.symmetric_difference(set2)); + } + } + } + + /// Test `split_at` on all possible arguments. + #[test] + fn split_at_is_equivalent_to_iterating() { + use Weekday::*; + + // `split_at()` is used in `iter()`, so we must not iterate + // over all days with `WeekdaySet::ALL.iter(Mon)`. + const WEEK: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun]; + + for weekdays in WeekdaySet::iter_all() { + for split_day in WEEK { + let expected_before: WeekdaySet = WEEK + .into_iter() + .take_while(|&day| day != split_day) + .filter(|&day| weekdays.contains(day)) + .collect(); + let expected_after: WeekdaySet = WEEK + .into_iter() + .skip_while(|&day| day != split_day) + .filter(|&day| weekdays.contains(day)) + .collect(); + + assert_eq!( + (expected_before, expected_after), + weekdays.split_at(split_day), + "split_at({split_day}) failed for {weekdays}", + ); + } + } + } +} diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000000..5fe42eb83f --- /dev/null +++ b/taplo.toml @@ -0,0 +1,4 @@ +include = ["deny.toml", "**/Cargo.toml"] + +[formatting] +inline_table_expand = false diff --git a/tests/android/tzdata b/tests/android/tzdata new file mode 100644 index 0000000000..ed1b3b061a Binary files /dev/null and b/tests/android/tzdata differ diff --git a/tests/dateutils.rs b/tests/dateutils.rs new file mode 100644 index 0000000000..474034e276 --- /dev/null +++ b/tests/dateutils.rs @@ -0,0 +1,162 @@ +#![cfg(all(unix, feature = "clock", feature = "std"))] + +use std::{path, process, thread}; + +#[cfg(target_os = "linux")] +use chrono::Days; +use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; + +fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { + let output = process::Command::new(path) + .arg("-d") + .arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour())) + .arg("+%Y-%m-%d %H:%M:%S %:z") + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + + // The below would be preferred. At this stage neither earliest() or latest() + // seems to be consistent with the output of the `date` command, so we simply + // compare both. + // let local = Local + // .with_ymd_and_hms(year, month, day, hour, 5, 1) + // // looks like the "date" command always returns a given time when it is ambiguous + // .earliest(); + + // if let Some(local) = local { + // assert_eq!(format!("{}\n", local), date_command_str); + // } else { + // // we are in a "Spring forward gap" due to DST, and so date also returns "" + // assert_eq!("", date_command_str); + // } + + // This is used while a decision is made whether the `date` output needs to + // be exactly matched, or whether MappedLocalTime::Ambiguous should be handled + // differently + + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { + chrono::MappedLocalTime::Ambiguous(a, b) => { + assert!(format!("{a}\n") == date_command_str || format!("{b}\n") == date_command_str) + } + chrono::MappedLocalTime::Single(a) => { + assert_eq!(format!("{a}\n"), date_command_str); + } + chrono::MappedLocalTime::None => { + assert_eq!("", date_command_str); + } + } +} + +/// path to Unix `date` command. Should work on most Linux and Unixes. Not the +/// path for MacOS (/bin/date) which uses a different version of `date` with +/// different arguments (so it won't run which is okay). +/// for testing only +#[allow(dead_code)] +#[cfg(not(target_os = "aix"))] +const DATE_PATH: &str = "/usr/bin/date"; +#[allow(dead_code)] +#[cfg(target_os = "aix")] +const DATE_PATH: &str = "/opt/freeware/bin/date"; + +#[cfg(test)] +/// test helper to sanity check the date command behaves as expected +/// asserts the command succeeded +fn assert_run_date_version() { + // note environment variable `LANG` + match std::env::var_os("LANG") { + Some(lang) => eprintln!("LANG: {lang:?}"), + None => eprintln!("LANG not set"), + } + let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap(); + let stdout = String::from_utf8(out.stdout).unwrap(); + let stderr = String::from_utf8(out.stderr).unwrap(); + // note the `date` binary version + eprintln!("command: {DATE_PATH:?} --version\nstdout: {stdout:?}\nstderr: {stderr:?}"); + assert!(out.status.success(), "command failed: {DATE_PATH:?} --version"); +} + +#[test] +fn try_verify_against_date_command() { + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {DATE_PATH:?} not found, skipping"); + return; + } + assert_run_date_version(); + + eprintln!("Run command {DATE_PATH:?} for every hour from 1975 to 2077, skipping some years...",); + + let mut children = vec![]; + for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() { + children.push(thread::spawn(|| { + let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN); + let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); + while date <= end { + verify_against_date_command_local(DATE_PATH, date); + date += chrono::TimeDelta::try_hours(1).unwrap(); + } + })); + } + for child in children { + // Wait for the thread to finish. Returns a result. + let _ = child.join(); + } +} + +#[cfg(target_os = "linux")] +fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) { + let required_format = + "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; + // a%a - depends from localization + // A%A - depends from localization + // b%b - depends from localization + // B%B - depends from localization + // h%h - depends from localization + // c%c - depends from localization + // p%p - depends from localization + // r%r - depends from localization + // x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D + // Z%Z - too many ways to represent it, will most likely fail + + let output = process::Command::new(path) + .env("LANG", "c") + .env("LC_ALL", "c") + .arg("-d") + .arg(format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + )) + .arg(format!("+{required_format}")) + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + let ldt = Local + .from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap()) + .unwrap(); + let formatted_date = format!("{}\n", ldt.format(required_format)); + assert_eq!(date_command_str, formatted_date); +} + +#[test] +#[cfg(target_os = "linux")] +fn try_verify_against_date_command_format() { + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {DATE_PATH:?} not found, skipping"); + return; + } + assert_run_date_version(); + + let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); + while date.year() < 2008 { + verify_against_date_command_format_local(DATE_PATH, date); + date = date + Days::new(55); + } +} diff --git a/tests/ohos/tzdata b/tests/ohos/tzdata new file mode 100644 index 0000000000..15400ef27e Binary files /dev/null and b/tests/ohos/tzdata differ diff --git a/tests/wasm.rs b/tests/wasm.rs index ab1d9e717d..c175efdf66 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,81 +1,89 @@ -#[cfg(all(test, feature = "wasmbind"))] -mod test { - extern crate chrono; - extern crate wasm_bindgen_test; +//! Run this test with: +//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind` +//! +//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with +//! the host system. +//! The check will fail if the local timezone does not match one of the timezones defined below. - use self::chrono::prelude::*; - use self::wasm_bindgen_test::*; +#![cfg(all( + target_family = "wasm", + feature = "wasmbind", + feature = "clock", + not(any(target_os = "emscripten", target_os = "wasi")) +))] - #[wasm_bindgen_test] - fn now() { - let utc: DateTime = Utc::now(); - let local: DateTime = Local::now(); +use chrono::prelude::*; +use wasm_bindgen_test::*; - // Ensure time set by the test script is correct - let now = env!("NOW"); - let actual = Utc.datetime_from_str(&now, "%s").unwrap(); - let diff = utc - actual; - assert!( - diff < chrono::Duration::minutes(5), - "expected {} - {} == {} < 5m (env var: {})", - utc, - actual, - diff, - now, - ); +#[wasm_bindgen_test] +fn now() { + let utc: DateTime = Utc::now(); + let local: DateTime = Local::now(); - let tz = env!("TZ"); - eprintln!("testing with tz={}", tz); + // Ensure time set by the test script is correct + let now = env!("NOW"); + let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc(); + let diff = utc - actual; + assert!( + diff < chrono::TimeDelta::try_minutes(5).unwrap(), + "expected {} - {} == {} < 5m (env var: {})", + utc, + actual, + diff, + now, + ); - // Ensure offset retrieved when getting local time is correct - let expected_offset = match tz { - "ACST-9:30" => FixedOffset::east(19 * 30 * 60), - "Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully - "EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60), - "EST" | "-0500" => FixedOffset::east(-5 * 60 * 60), - "UTC0" | "+0000" => FixedOffset::east(0), - tz => panic!("unexpected TZ {}", tz), - }; - assert_eq!( - &expected_offset, - local.offset(), - "expected: {:?} local: {:?}", - expected_offset, - local.offset(), - ); - } + let tz = env!("TZ"); + eprintln!("testing with tz={}", tz); - #[wasm_bindgen_test] - fn from_is_exact() { - let now = js_sys::Date::new_0(); + // Ensure offset retrieved when getting local time is correct + let expected_offset = match tz { + "ACST-9:30" => FixedOffset::east_opt(19 * 30 * 60).unwrap(), + "Asia/Katmandu" => FixedOffset::east_opt(23 * 15 * 60).unwrap(), // No DST thankfully + "EDT" | "EST4" | "-0400" => FixedOffset::east_opt(-4 * 60 * 60).unwrap(), + "EST" | "-0500" => FixedOffset::east_opt(-5 * 60 * 60).unwrap(), + "UTC0" | "+0000" => FixedOffset::east_opt(0).unwrap(), + tz => panic!("unexpected TZ {}", tz), + }; + assert_eq!( + &expected_offset, + local.offset(), + "expected: {:?} local: {:?}", + expected_offset, + local.offset(), + ); +} + +#[wasm_bindgen_test] +fn from_is_exact() { + let now = js_sys::Date::new_0(); - let dt = DateTime::::from(now.clone()); + let dt = DateTime::::from(now.clone()); - assert_eq!(now.get_time() as i64, dt.timestamp_millis()); - } + assert_eq!(now.get_time() as i64, dt.timestamp_millis()); +} - #[wasm_bindgen_test] - fn local_from_local_datetime() { - let now = Local::now(); - let ndt = now.naive_local(); - let res = match Local.from_local_datetime(&ndt).single() { - Some(v) => v, - None => panic! {"Required for test!"}, - }; - assert_eq!(now, res); - } +#[wasm_bindgen_test] +fn local_from_local_datetime() { + let now = Local::now(); + let ndt = now.naive_local(); + let res = match Local.from_local_datetime(&ndt).single() { + Some(v) => v, + None => panic! {"Required for test!"}, + }; + assert_eq!(now, res); +} - #[wasm_bindgen_test] - fn convert_all_parts_with_milliseconds() { - let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); - let js_date = js_sys::Date::from(time); +#[wasm_bindgen_test] +fn convert_all_parts_with_milliseconds() { + let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); + let js_date = js_sys::Date::from(time); - assert_eq!(js_date.get_utc_full_year(), 2020); - assert_eq!(js_date.get_utc_month(), 12); - assert_eq!(js_date.get_utc_date(), 1); - assert_eq!(js_date.get_utc_hours(), 3); - assert_eq!(js_date.get_utc_minutes(), 1); - assert_eq!(js_date.get_utc_seconds(), 55); - assert_eq!(js_date.get_utc_milliseconds(), 974); - } + assert_eq!(js_date.get_utc_full_year(), 2020); + assert_eq!(js_date.get_utc_month(), 11); // months are numbered 0..=11 + assert_eq!(js_date.get_utc_date(), 1); + assert_eq!(js_date.get_utc_hours(), 3); + assert_eq!(js_date.get_utc_minutes(), 1); + assert_eq!(js_date.get_utc_seconds(), 55); + assert_eq!(js_date.get_utc_milliseconds(), 974); } diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs new file mode 100644 index 0000000000..6a38dcecba --- /dev/null +++ b/tests/win_bindings.rs @@ -0,0 +1,39 @@ +use std::fs; +use windows_bindgen::bindgen; + +#[test] +fn gen_bindings() { + let existing = fs::read_to_string(BINDINGS).unwrap(); + + bindgen([ + "--out", + BINDINGS, + "--flat", + "--no-comment", + "--no-deps", + "--sys", + "--filter", + "GetTimeZoneInformationForYear", + "SystemTimeToFileTime", + "SystemTimeToTzSpecificLocalTime", + "TzSpecificLocalTimeToSystemTime", + ]) + .unwrap(); + + // Check the output is the same as before. + // Depending on the git configuration the file may have been checked out with `\r\n` newlines or + // with `\n`. Compare line-by-line to ignore this difference. + let mut new = fs::read_to_string(BINDINGS).unwrap(); + if existing.contains("\r\n") && !new.contains("\r\n") { + new = new.replace("\n", "\r\n"); + } else if !existing.contains("\r\n") && new.contains("\r\n") { + new = new.replace("\r\n", "\n"); + } + + similar_asserts::assert_eq!(existing, new); + if !new.lines().eq(existing.lines()) { + panic!("generated file `{BINDINGS}` is changed."); + } +} + +const BINDINGS: &str = "src/offset/local/win_bindings.rs";