aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Walbran <qwandor@google.com>2024-02-21 17:10:36 +0000
committerAndrew Walbran <qwandor@google.com>2024-03-21 18:01:29 +0000
commitc0dc6153e65acbd4b59926eaccf67cefa1bc6c5a (patch)
tree66b64b0104978d06a3dc21f73e3dc97ff024514a
parent894d6ced8fe0e14fd3867562e978fadec89ad94e (diff)
downloadchrono-c0dc6153e65acbd4b59926eaccf67cefa1bc6c5a.tar.gz
Upgrade chrono to 0.4.34
This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update external/rust/crates/chrono For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Bug: 326256145 Test: TreeHugger Change-Id: Ibaddaa66db364a9d9d86be6f6cea06f3d57ba6d1
-rw-r--r--.cargo_vcs_info.json7
-rw-r--r--.github/codecov.yml5
-rw-r--r--.github/dependabot.yml17
-rw-r--r--.github/pull_request_template.md8
-rw-r--r--.github/workflows/codecov.yml33
-rw-r--r--.github/workflows/lint.yml70
-rw-r--r--.github/workflows/test-release.yml109
-rw-r--r--.github/workflows/test.yml257
-rw-r--r--.gitignore4
-rw-r--r--AUTHORS.txt41
-rw-r--r--Android.bp13
-rw-r--r--CHANGELOG.md15
-rw-r--r--CITATION.cff33
-rw-r--r--Cargo.toml145
-rw-r--r--Cargo.toml.orig77
-rw-r--r--METADATA25
-rw-r--r--README.md440
-rw-r--r--appveyor.yml12
-rw-r--r--benches/chrono.rs116
-rw-r--r--benches/serde.rs30
-rw-r--r--deny.toml11
-rw-r--r--src/date.rs293
-rw-r--r--src/datetime.rs2589
-rw-r--r--src/datetime/mod.rs1841
-rw-r--r--src/datetime/rustc_serialize.rs124
-rw-r--r--src/datetime/serde.rs1256
-rw-r--r--src/datetime/tests.rs1638
-rw-r--r--src/div.rs41
-rw-r--r--src/format/formatting.rs954
-rw-r--r--src/format/locales.rs116
-rw-r--r--src/format/mod.rs738
-rw-r--r--src/format/parse.rs1881
-rw-r--r--src/format/parsed.rs232
-rw-r--r--src/format/scan.rs340
-rw-r--r--src/format/strftime.rs1302
-rw-r--r--src/lib.rs1555
-rw-r--r--src/month.rs443
-rw-r--r--src/naive/date.rs2845
-rw-r--r--src/naive/datetime.rs2507
-rw-r--r--src/naive/datetime/mod.rs2309
-rw-r--r--src/naive/datetime/rustc_serialize.rs75
-rw-r--r--src/naive/datetime/serde.rs1180
-rw-r--r--src/naive/datetime/tests.rs593
-rw-r--r--src/naive/internals.rs477
-rw-r--r--src/naive/isoweek.rs133
-rw-r--r--src/naive/mod.rs39
-rw-r--r--src/naive/time.rs1814
-rw-r--r--src/naive/time/mod.rs1612
-rw-r--r--src/naive/time/rustc_serialize.rs30
-rw-r--r--src/naive/time/serde.rs69
-rw-r--r--src/naive/time/tests.rs389
-rw-r--r--src/offset/fixed.rs212
-rw-r--r--src/offset/local.rs227
-rw-r--r--src/offset/local/mod.rs537
-rw-r--r--src/offset/local/tz_info/mod.rs116
-rw-r--r--src/offset/local/tz_info/parser.rs333
-rw-r--r--src/offset/local/tz_info/rule.rs1045
-rw-r--r--src/offset/local/tz_info/timezone.rs950
-rw-r--r--src/offset/local/unix.rs171
-rw-r--r--src/offset/local/win_bindings.rs71
-rw-r--r--src/offset/local/win_bindings.txt7
-rw-r--r--src/offset/local/windows.rs262
-rw-r--r--src/offset/mod.rs308
-rw-r--r--src/offset/utc.rs98
-rw-r--r--src/oldtime.rs684
-rw-r--r--src/round.rs633
-rw-r--r--src/sys.rs126
-rw-r--r--src/sys/stub.rs80
-rw-r--r--src/sys/unix.rs126
-rw-r--r--src/sys/windows.rs131
-rw-r--r--src/time_delta.rs1215
-rw-r--r--src/traits.rs389
-rw-r--r--src/weekday.rs398
-rw-r--r--taplo.toml4
-rw-r--r--tests/dateutils.rs162
-rw-r--r--tests/wasm.rs136
-rw-r--r--tests/win_bindings.rs22
77 files changed, 25481 insertions, 13845 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index d8ac64d..4002f10 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
{
"git": {
- "sha1": "4eeedcfcc409f19d965f477d767d05f3418c4df1"
- }
-}
+ "sha1": "dc196062650c05528cbe259e340210f0340a05d1"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 0000000..236a6f1
--- /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 0000000..764cd26
--- /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 14286bd..55d575a 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 0000000..520a39e
--- /dev/null
+++ b/.github/workflows/codecov.yml
@@ -0,0 +1,33 @@
+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 rustc-serialize serde arbitrary"
+
+on:
+ push:
+ branches: [main, 0.4.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
+ env:
+ CARGO_TERM_COLOR: always
+ steps:
+ - uses: actions/checkout@v4
+ # nightly is required for --doctests, see cargo-llvm-cov#2
+ - name: Install Rust (nightly)
+ run: rustup update nightly
+ - 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@v4
+ 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 711e381..18f65d7 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,25 +1,71 @@
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 rustc-serialize serde arbitrary"
+
on:
push:
- branches: [main, master]
+ branches: [main, 0.4.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@v4
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc
+ - 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 --manifest-path fuzz/Cargo.toml --color=always \
+ -- -D warnings
+ - run: |
+ cargo clippy --manifest-path bench/Cargo.toml --color=always \
+ -- -D warnings
+ env:
+ RUSTFLAGS: "-Dwarnings"
+
+ toml:
+ runs-on: ubuntu-latest
+ container:
+ image: tamasfe/taplo:0.8.0
+ steps:
+ - uses: actions/checkout@v4
+ - run: taplo lint
+ - run: taplo fmt --check --diff
+
+ cargo-deny:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: EmbarkStudios/cargo-deny-action@v1
+
+ check-doc:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - 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@v4
with:
- toolchain: stable
- override: true
- - 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 7913bfd..0000000
--- a/.github/workflows/test-release.yml
+++ /dev/null
@@ -1,109 +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-16.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-16.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
-
- - 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
-
- - 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
-
- - 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 1f1e5d8..18ae42c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,134 +1,201 @@
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 rustc-serialize serde arbitrary"
+
on:
push:
- branches: [main]
+ branches: [main, 0.4.x]
pull_request:
- branches: [main]
- paths:
- - '**.rs'
- - .github/**
- - Cargo.toml
jobs:
- test:
+ timezones:
strategy:
matrix:
- os: [ubuntu-16.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-16.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@v4
+ - uses: dtolnay/rust-toolchain@stable
+ - 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@v4
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: "1.61.0"
+ - uses: Swatinem/rust-cache@v2
+ # run --lib and --doc to avoid the long running integration tests
+ # which are run elsewhere
+ - run: |
+ cargo test --lib \
+ --features \
+ unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \
+ --color=always -- --color=always
+ - run: |
+ cargo test --doc \
+ --features \
+ unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \
+ --color=always -- --color=always
+
+ rust_versions:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ rust_version: ["stable", "beta", "nightly"]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust_version }}
- override: true
-
- - name: Build and Test
- run: bash ci/github.sh
+ - 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, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ - 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,pure-rust-locales,libc,winapi,rkyv-16,rkyv-64,rkyv-validation \
+ --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@v4
+ - uses: dtolnay/rust-toolchain@stable
with:
- toolchain: stable
- target: thumbv6m-none-eabi
- override: true
-
- - 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@v4
+ - uses: dtolnay/rust-toolchain@stable
with:
- toolchain: stable
- target: wasm32-unknown-unknown
- override: true
+ 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@v4
+ - 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
+ - uses: Swatinem/rust-cache@v2
+ - uses: actions/setup-node@v4
+ - 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_wasi:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ target:
+ - wasm32-wasi
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: wasm32-wasi
+ - uses: Swatinem/rust-cache@v2
+ - run: cargo install cargo-wasi
+ - uses: mwilliamson/setup-wasmtime-action@v2
+ with:
+ wasmtime-version: "12.0.1"
+ # We can't use `--all-features` because `rustc-serialize` doesn't support
+ # `wasm32-wasi`.
+ - run: cargo wasi test --features=serde,unstable-locales --color=always -- --color=always
cross-targets:
strategy:
matrix:
target:
- x86_64-sun-solaris
-
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
-
- - name: Install cross
- run: bash ci/install-cross.sh
+ - uses: actions/checkout@v4
+ - run: cargo install cross
+ - uses: Swatinem/rust-cache@v2
+ - run: cross check --target ${{ matrix.target }}
- - 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@v4
+ - 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
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@nightly
+ - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps
+ env:
+ RUSTDOCFLAGS: "-D warnings --cfg docsrs"
diff --git a/.gitignore b/.gitignore
index a9d37c5..f989181 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 9501a9d..0000000
--- a/AUTHORS.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-Chrono is mainly written by Kang Seonghoon <public+rust@mearie.org>,
-and also the following people (in ascending order):
-
-Alex Mikhalev <alexmikhalevalex@gmail.com>
-Alexander Bulaev <alexbool@yandex-team.ru>
-Ashley Mannix <ashleymannix@live.com.au>
-Ben Boeckel <mathstuf@gmail.com>
-Ben Eills <ben@beneills.com>
-Brandon W Maister <bwm@knewton.com>
-Brandon W Maister <quodlibetor@gmail.com>
-Cecile Tonglet <cecile.tonglet@cecton.com>
-Colin Ray <r.colinray@gmail.com>
-Corey Farwell <coreyf@rwell.org>
-Dan <dan@ebip.co.uk>
-Danilo Bargen <mail@dbrgn.ch>
-David Hewson <dev@daveid.co.uk>
-David Ross <daboross@daboross.net>
-David Tolnay <dtolnay@gmail.com>
-David Willie <david.willie.1@gmail.com>
-Eric Findlay <e.findlay@protonmail.ch>
-Eunchong Yu <kroisse@gmail.com>
-Frans Skarman <frans.skarman@gmail.com>
-Huon Wilson <dbau.pp+github@gmail.com>
-Igor Gnatenko <ignatenko@redhat.com>
-Jim Turner <jturner314@gmail.com>
-Jisoo Park <xxxyel@gmail.com>
-Joe Wilm <joe@jwilm.com>
-John Heitmann <jheitmann@gmail.com>
-John Nagle <nagle@sitetruth.com>
-Jonas mg <jonasmg@yepmail.net>
-János Illés <ijanos@gmail.com>
-Ken Tossell <ken@tossell.net>
-Martin Risell Lilja <martin.risell.lilja@gmail.com>
-Richard Petrie <rap1011@ksu.edu>
-Ryan Lewis <ryansname@gmail.com>
-Sergey V. Shadoy <shadoysv@yandex.ru>
-Sergey V. Galtsev <sergey.v.galtsev@github.com>
-Steve Klabnik <steve@steveklabnik.com>
-Tom Gallacher <tomgallacher23@gmail.com>
-klutzy <klutzytheklutzy@gmail.com>
-kud1ing <github@kudling.de>
diff --git a/Android.bp b/Android.bp
index ef12cc4..85d96a3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,19 +36,22 @@ rust_library {
host_supported: true,
crate_name: "chrono",
cargo_env_compat: true,
- cargo_pkg_version: "0.4.19",
+ cargo_pkg_version: "0.4.34",
srcs: ["src/lib.rs"],
- edition: "2015",
+ edition: "2021",
features: [
+ "alloc",
+ "android-tzdata",
"clock",
- "libc",
+ "iana-time-zone",
+ "now",
"serde",
"std",
"winapi",
+ "windows-targets",
],
rustlibs: [
- "liblibc",
- "libnum_integer",
+ "libiana_time_zone",
"libnum_traits",
"libserde",
],
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be289ae..1e6f6f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +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)
+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
@@ -737,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 0000000..904009d
--- /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: 2024-02-11
+version: 0.4.34
+
+# 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 2d3e764..9f0f82f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,28 +3,42 @@
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies
+# to registry (e.g., crates.io) dependencies.
#
-# If you believe there's an error in this file please file an
-# issue against the rust-lang/cargo repository. If you're
-# editing this file be aware that the upstream Cargo.toml
-# will likely look very different (and much more reasonable)
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
[package]
+edition = "2021"
+rust-version = "1.61.0"
name = "chrono"
-version = "0.4.19"
-authors = ["Kang Seonghoon <public+rust@mearie.org>", "Brandon W Maister <quodlibetor@gmail.com>"]
-exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"]
+version = "0.4.34"
+exclude = ["/ci/*"]
description = "Date and time library for Rust"
homepage = "https://github.com/chronotope/chrono"
documentation = "https://docs.rs/chrono/"
readme = "README.md"
-keywords = ["date", "time", "calendar"]
+keywords = [
+ "date",
+ "time",
+ "calendar",
+]
categories = ["date-and-time"]
-license = "MIT/Apache-2.0"
+license = "MIT OR Apache-2.0"
repository = "https://github.com/chronotope/chrono"
+
[package.metadata.docs.rs]
-features = ["serde"]
+features = [
+ "arbitrary",
+ "rkyv",
+ "serde",
+ "unstable-locales",
+]
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
[package.metadata.playground]
features = ["serde"]
@@ -32,31 +46,24 @@ features = ["serde"]
[lib]
name = "chrono"
-[[bench]]
-name = "chrono"
-harness = false
-required-features = ["__internal_bench"]
-
-[[bench]]
-name = "serde"
-harness = false
-required-features = ["serde"]
-[dependencies.libc]
-version = "0.2.69"
+[dependencies.arbitrary]
+version = "1.0.0"
+features = ["derive"]
optional = true
-[dependencies.num-integer]
-version = "0.1.36"
-default-features = false
-
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.pure-rust-locales]
-version = "0.5.2"
+version = "0.8"
optional = true
+[dependencies.rkyv]
+version = "0.7.43"
+optional = true
+default-features = false
+
[dependencies.rustc-serialize]
version = "0.3.20"
optional = true
@@ -66,21 +73,8 @@ version = "1.0.99"
optional = true
default-features = false
-[dependencies.time]
-version = "0.1.43"
-optional = true
[dev-dependencies.bincode]
-version = "0.8.0"
-
-[dev-dependencies.criterion]
-version = "0.3"
-
-[dev-dependencies.doc-comment]
-version = "0.3"
-
-[dev-dependencies.num-iter]
-version = "0.1.35"
-default-features = false
+version = "1.3.0"
[dev-dependencies.serde_derive]
version = "1"
@@ -90,15 +84,48 @@ default-features = false
version = "1"
[features]
-__doctest = []
__internal_bench = []
alloc = []
-clock = ["libc", "std", "winapi"]
-default = ["clock", "std", "oldtime"]
-oldtime = ["time"]
-std = []
-unstable-locales = ["pure-rust-locales", "alloc"]
-wasmbind = ["wasm-bindgen", "js-sys"]
+clock = [
+ "winapi",
+ "iana-time-zone",
+ "android-tzdata",
+ "now",
+]
+default = [
+ "clock",
+ "std",
+ "oldtime",
+ "wasmbind",
+]
+libc = []
+now = ["std"]
+oldtime = []
+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"]
+std = ["alloc"]
+unstable-locales = ["pure-rust-locales"]
+wasmbind = [
+ "wasm-bindgen",
+ "js-sys",
+]
+winapi = ["windows-targets"]
+
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys]
version = "0.3"
optional = true
@@ -106,14 +133,22 @@ optional = true
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.wasm-bindgen]
version = "0.2"
optional = true
+
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dev-dependencies.wasm-bindgen-test]
version = "0.3"
-[target."cfg(windows)".dependencies.winapi]
-version = "0.3.0"
-features = ["std", "minwinbase", "minwindef", "timezoneapi"]
+
+[target."cfg(target_os = \"android\")".dependencies.android-tzdata]
+version = "0.1.1"
+optional = true
+
+[target."cfg(unix)".dependencies.iana-time-zone]
+version = "0.1.45"
+features = ["fallback"]
+optional = true
+
+[target."cfg(windows)".dependencies.windows-targets]
+version = "0.52"
optional = true
-[badges.appveyor]
-repository = "chronotope/chrono"
-[badges.travis-ci]
-repository = "chronotope/chrono"
+[target."cfg(windows)".dev-dependencies.windows-bindgen]
+version = "0.52"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 6b4d48a..04e427f 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,11 +1,6 @@
[package]
name = "chrono"
-version = "0.4.19"
-authors = [
- "Kang Seonghoon <public+rust@mearie.org>",
- "Brandon W Maister <quodlibetor@gmail.com>",
-]
-
+version = "0.4.34"
description = "Date and time library for Rust"
homepage = "https://github.com/chronotope/chrono"
documentation = "https://docs.rs/chrono/"
@@ -13,66 +8,70 @@ 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"
+exclude = ["/ci/*"]
+edition = "2021"
+rust-version = "1.61.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"]
+libc = []
+winapi = ["windows-targets"]
+std = ["alloc"]
+clock = ["winapi", "iana-time-zone", "android-tzdata", "now"]
+now = ["std"]
+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", optional = true }
+rkyv = { version = "0.7.43", optional = true, default-features = false }
+arbitrary = { version = "1.0.0", features = ["derive"], optional = true }
[target.'cfg(all(target_arch = "wasm32", 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-targets = { version = "0.52", optional = true }
+
+[target.'cfg(windows)'.dev-dependencies]
+windows-bindgen = { version = "0.52" }
+
+[target.'cfg(unix)'.dependencies]
+iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] }
+
+[target.'cfg(target_os = "android")'.dependencies]
+android-tzdata = { version = "0.1.1", optional = true }
[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" }
+bincode = { version = "1.3.0" }
[target.'cfg(all(target_arch = "wasm32", 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/METADATA b/METADATA
index e33e88e..ea84889 100644
--- a/METADATA
+++ b/METADATA
@@ -1,19 +1,20 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/rust/crates/chrono
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
name: "chrono"
description: "Date and time library for Rust"
third_party {
- url {
- type: HOMEPAGE
- value: "https://crates.io/crates/chrono"
- }
- url {
- type: ARCHIVE
- value: "https://static.crates.io/crates/chrono/chrono-0.4.19.crate"
- }
- version: "0.4.19"
license_type: NOTICE
last_upgrade_date {
- year: 2021
- month: 1
- day: 28
+ year: 2024
+ month: 2
+ day: 21
+ }
+ homepage: "https://crates.io/crates/chrono"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/chrono/chrono-0.4.34.crate"
+ version: "0.4.34"
}
}
diff --git a/README.md b/README.md
index 5a5a74b..0558d88 100644
--- a/README.md
+++ b/README.md
@@ -1,419 +1,83 @@
-[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
+ [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.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> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
-let local: DateTime<Local> = 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
-
-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.
-
-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`:
-
-```text
-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<FixedOffset>`, `DateTime<Utc>` and
- `DateTime<Local>` 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<FixedOffset>`.
- 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::<DateTime<Utc>>(), Ok(dt.clone()));
-assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<Utc>>(), Ok(dt.clone()));
-assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<FixedOffset>>(), 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<Utc>`](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`: Enable serialization/deserialization via serde.
+* `rkyv`: Enable serialization/deserialization via rkyv.
+* `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated).
+* `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.
-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.61.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
new file mode 100644
index 0000000..6057e85
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,12 @@
+# 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/benches/chrono.rs b/benches/chrono.rs
deleted file mode 100644
index 1c64063..0000000
--- 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::<Utc>::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: Datelike>(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/benches/serde.rs b/benches/serde.rs
deleted file mode 100644
index 860b06e..0000000
--- a/benches/serde.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-extern crate chrono;
-extern crate criterion;
-
-use criterion::{black_box, criterion_group, criterion_main, Criterion};
-
-use chrono::NaiveDateTime;
-
-fn bench_ser_naivedatetime_string(c: &mut Criterion) {
- c.bench_function("bench_ser_naivedatetime_string", |b| {
- let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap();
- b.iter(|| {
- black_box(serde_json::to_string(&dt)).unwrap();
- });
- });
-}
-
-fn bench_ser_naivedatetime_writer(c: &mut Criterion) {
- c.bench_function("bench_ser_naivedatetime_writer", |b| {
- let mut s: Vec<u8> = Vec::with_capacity(20);
- let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap();
- b.iter(|| {
- let s = &mut s;
- s.clear();
- black_box(serde_json::to_writer(s, &dt)).unwrap();
- });
- });
-}
-
-criterion_group!(benches, bench_ser_naivedatetime_writer, bench_ser_naivedatetime_string);
-criterion_main!(benches);
diff --git a/deny.toml b/deny.toml
new file mode 100644
index 0000000..186290e
--- /dev/null
+++ b/deny.toml
@@ -0,0 +1,11 @@
+[licenses]
+allow-osi-fsf-free = "either"
+copyleft = "deny"
+
+[advisories]
+ignore = [
+ "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility
+]
+unmaintained = "deny"
+unsound = "deny"
+yanked = "deny"
diff --git a/src/date.rs b/src/date.rs
index 0012d36..a66882c 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -2,73 +2,88 @@
// 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.
///
-/// This type should be considered ambiguous at best,
-/// due to the inherent lack of precision required for the time zone resolution.
-/// For serialization and deserialization uses, it is best to use `NaiveDate` instead.
+/// You almost certainly want to be using a [`NaiveDate`] instead of this type.
+///
+/// This type primarily exists to aid in the construction of DateTimes that
+/// have a timezone by way of the [`TimeZone`] datelike constructors (e.g.
+/// [`TimeZone::ymd`]).
+///
+/// This type should be considered ambiguous at best, due to the inherent lack
+/// of precision required for the time zone resolution.
+///
/// There are some guarantees on the usage of `Date<Tz>`:
///
-/// - If properly constructed via `TimeZone::ymd` and others without an error,
+/// - If properly constructed via [`TimeZone::ymd`] and others without an error,
/// 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` to the local date,
-/// as long as that offset did occur in given day.
+/// - 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`,
/// it may produce either `2015-03-08-08:00` or `2015-03-08-07:00`
/// but *not* `2015-03-08+00:00` and others.
///
-/// - 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)`.
+/// - 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_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<Tz>` instead")]
#[derive(Clone)]
+#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
pub struct Date<Tz: TimeZone> {
date: NaiveDate,
offset: Tz::Offset,
}
/// The minimum possible `Date`.
-pub const MIN_DATE: Date<Utc> = 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<Utc> = Date::<Utc>::MIN_UTC;
/// The maximum possible `Date`.
-pub const MAX_DATE: Date<Utc> = 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<Utc> = Date::<Utc>::MAX_UTC;
impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
- 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<DateTime<Tz>> {
let localdt = self.naive_local().and_time(time);
self.timezone().from_local_datetime(&localdt).single()
@@ -78,7 +93,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.and_hms_opt(hour, min, sec).expect("invalid time")
}
@@ -88,6 +105,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// 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<DateTime<Tz>> {
NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time))
}
@@ -97,7 +115,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time")
}
@@ -108,6 +128,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
#[inline]
+ #[must_use]
pub fn and_hms_milli_opt(
&self,
hour: u32,
@@ -123,7 +144,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time")
}
@@ -134,6 +157,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
#[inline]
+ #[must_use]
pub fn and_hms_micro_opt(
&self,
hour: u32,
@@ -149,7 +173,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time")
}
@@ -160,6 +186,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
#[inline]
+ #[must_use]
pub fn and_hms_nano_opt(
&self,
hour: u32,
@@ -173,7 +200,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.succ_opt().expect("out of bound")
}
@@ -182,6 +211,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` when `self` is the last representable date.
#[inline]
+ #[must_use]
pub fn succ_opt(&self) -> Option<Date<Tz>> {
self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
@@ -189,7 +219,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz> {
self.pred_opt().expect("out of bound")
}
@@ -198,18 +230,21 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` when `self` is the first representable date.
#[inline]
+ #[must_use]
pub fn pred_opt(&self) -> Option<Date<Tz>> {
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)
}
@@ -217,40 +252,45 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<Tz2: TimeZone>(&self, tz: &Tz2) -> Date<Tz2> {
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<Date<Tz>> {
- 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<Date<Tz>> {
+ 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<Date<Tz>> {
- 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<Date<Tz>> {
+ 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<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> OldDuration {
+ #[must_use]
+ pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> 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
}
@@ -261,9 +301,21 @@ impl<Tz: TimeZone> Date<Tz> {
/// 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<u32> {
+ self.date.years_since(base.date)
+ }
+
+ /// The minimum possible `Date`.
+ pub const MIN_UTC: Date<Utc> = Date { date: NaiveDate::MIN, offset: Utc };
+ /// The maximum possible `Date`.
+ pub const MAX_UTC: Date<Utc> = Date { date: NaiveDate::MAX, offset: Utc };
}
/// Maps the local date to other date with given conversion function.
@@ -279,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<I>
where
I: Iterator<Item = B> + Clone,
@@ -290,17 +343,19 @@ where
}
/// Formats the date with the specified format string.
- /// See the [`format::strftime` module](./format/strftime/index.html)
+ /// See the [`crate::format::strftime`] module
/// on the supported escape sequences.
- #[cfg(any(feature = "alloc", feature = "std", test))]
+ #[cfg(feature = "alloc")]
#[inline]
+ #[must_use]
pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
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,
@@ -320,10 +375,11 @@ where
}
/// Formats the date with the specified format string and locale.
- /// See the [`format::strftime` module](./format/strftime/index.html)
+ /// 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,
@@ -421,7 +477,7 @@ impl<Tz: TimeZone> Eq for Date<Tz> {}
impl<Tz: TimeZone> PartialOrd for Date<Tz> {
fn partial_cmp(&self, other: &Date<Tz>) -> Option<Ordering> {
- self.date.partial_cmp(&other.date)
+ Some(self.cmp(other))
}
}
@@ -437,36 +493,51 @@ impl<Tz: TimeZone> hash::Hash for Date<Tz> {
}
}
-impl<Tz: TimeZone> Add<OldDuration> for Date<Tz> {
+impl<Tz: TimeZone> Add<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
- fn add(self, rhs: OldDuration) -> Date<Tz> {
- self.checked_add_signed(rhs).expect("`Date + Duration` overflowed")
+ fn add(self, rhs: TimeDelta) -> Date<Tz> {
+ self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed")
+ }
+}
+
+impl<Tz: TimeZone> AddAssign<TimeDelta> for Date<Tz> {
+ #[inline]
+ fn add_assign(&mut self, rhs: TimeDelta) {
+ self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed");
}
}
-impl<Tz: TimeZone> Sub<OldDuration> for Date<Tz> {
+impl<Tz: TimeZone> Sub<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
- fn sub(self, rhs: OldDuration) -> Date<Tz> {
- self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed")
+ fn sub(self, rhs: TimeDelta) -> Date<Tz> {
+ self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed")
+ }
+}
+
+impl<Tz: TimeZone> SubAssign<TimeDelta> for Date<Tz> {
+ #[inline]
+ fn sub_assign(&mut self, rhs: TimeDelta) {
+ self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed");
}
}
impl<Tz: TimeZone> Sub<Date<Tz>> for Date<Tz> {
- type Output = OldDuration;
+ type Output = TimeDelta;
#[inline]
- fn sub(self, rhs: Date<Tz>) -> OldDuration {
+ fn sub(self, rhs: Date<Tz>) -> TimeDelta {
self.signed_duration_since(rhs)
}
}
impl<Tz: TimeZone> fmt::Debug for Date<Tz> {
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)
}
}
@@ -475,6 +546,118 @@ 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)
+ }
+}
+
+// Note that implementation of Arbitrary cannot be automatically derived for Date<Tz>, due to
+// the nontrivial bound <Tz as TimeZone>::Offset: Arbitrary.
+#[cfg(all(feature = "arbitrary", feature = "std"))]
+impl<'a, Tz> arbitrary::Arbitrary<'a> for Date<Tz>
+where
+ Tz: TimeZone,
+ <Tz as TimeZone>::Offset: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Date<Tz>> {
+ let date = NaiveDate::arbitrary(u)?;
+ let offset = <Tz as TimeZone>::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::<Utc>::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::<Utc>::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 ecd4642..0000000
--- a/src/datetime.rs
+++ /dev/null
@@ -1,2589 +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, 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<Tz: TimeZone> {
- datetime: NaiveDateTime,
- offset: Tz::Offset,
-}
-
-/// The minimum possible `DateTime<Utc>`.
-pub const MIN_DATETIME: DateTime<Utc> = DateTime { datetime: naive::MIN_DATETIME, offset: Utc };
-/// The maximum possible `DateTime<Utc>`.
-pub const MAX_DATETIME: DateTime<Utc> = DateTime { datetime: naive::MAX_DATETIME, offset: Utc };
-
-impl<Tz: TimeZone> DateTime<Tz> {
- /// 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::<Utc>::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<Tz> {
- DateTime { datetime: datetime, offset: offset }
- }
-
- /// Retrieves a date component.
- #[inline]
- pub fn date(&self) -> Date<Tz> {
- Date::from_utc(self.naive_local().date(), self.offset.clone())
- }
-
- /// 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-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<Tz2: TimeZone>(&self, tz: &Tz2) -> DateTime<Tz2> {
- 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<DateTime<Tz>> {
- 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<DateTime<Tz>> {
- 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<Tz2: TimeZone>(self, rhs: DateTime<Tz2>) -> 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()
- }
-}
-
-/// Convert a `DateTime<Utc>` instance into a `DateTime<FixedOffset>` instance.
-impl From<DateTime<Utc>> for DateTime<FixedOffset> {
- /// Convert this `DateTime<Utc>` instance into a `DateTime<FixedOffset>` 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<Utc>) -> Self {
- src.with_timezone(&FixedOffset::east(0))
- }
-}
-
-/// Convert a `DateTime<Utc>` instance into a `DateTime<Local>` instance.
-#[cfg(feature = "clock")]
-impl From<DateTime<Utc>> for DateTime<Local> {
- /// Convert this `DateTime<Utc>` instance into a `DateTime<Local>` instance.
- ///
- /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones.
- fn from(src: DateTime<Utc>) -> Self {
- src.with_timezone(&Local)
- }
-}
-
-/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
-impl From<DateTime<FixedOffset>> for DateTime<Utc> {
- /// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
- ///
- /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone
- /// difference.
- fn from(src: DateTime<FixedOffset>) -> Self {
- src.with_timezone(&Utc)
- }
-}
-
-/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
-#[cfg(feature = "clock")]
-impl From<DateTime<FixedOffset>> for DateTime<Local> {
- /// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
- ///
- /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local
- /// time.
- fn from(src: DateTime<FixedOffset>) -> Self {
- src.with_timezone(&Local)
- }
-}
-
-/// Convert a `DateTime<Local>` instance into a `DateTime<Utc>` instance.
-#[cfg(feature = "clock")]
-impl From<DateTime<Local>> for DateTime<Utc> {
- /// Convert this `DateTime<Local>` instance into a `DateTime<Utc>` instance.
- ///
- /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in
- /// timezones.
- fn from(src: DateTime<Local>) -> Self {
- src.with_timezone(&Utc)
- }
-}
-
-/// Convert a `DateTime<Local>` instance into a `DateTime<FixedOffset>` instance.
-#[cfg(feature = "clock")]
-impl From<DateTime<Local>> for DateTime<FixedOffset> {
- /// Convert this `DateTime<Local>` instance into a `DateTime<FixedOffset>` 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<Local>) -> Self {
- src.with_timezone(&FixedOffset::east(0))
- }
-}
-
-/// Maps the local datetime to other datetime with given conversion function.
-fn map_local<Tz: TimeZone, F>(dt: &DateTime<Tz>, mut f: F) -> Option<DateTime<Tz>>
-where
- F: FnMut(NaiveDateTime) -> Option<NaiveDateTime>,
-{
- f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single())
-}
-
-impl DateTime<FixedOffset> {
- /// 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<DateTime<FixedOffset>> {
- 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<DateTime<FixedOffset>> {
- 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](./format/strftime/index.html)
- /// on the supported escape sequences.
- ///
- /// See also `Offset::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`](./naive/struct.NaiveDateTime.html#method.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<DateTime<FixedOffset>> {
- let mut parsed = Parsed::new();
- parse(&mut parsed, s, StrftimeItems::new(fmt))?;
- parsed.to_datetime()
- }
-}
-
-impl<Tz: TimeZone> DateTime<Tz>
-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](format/enum.Fixed.html#variant.TimezoneOffsetColonZ).
- /// If passed `use_z` false, use
- /// [Fixed::TimezoneOffsetColon](format/enum.Fixed.html#variant.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<I>
- where
- I: Iterator<Item = B> + Clone,
- B: Borrow<Item<'a>>,
- {
- 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](./format/strftime/index.html)
- /// on the supported escape sequences.
- #[cfg(any(feature = "alloc", feature = "std", test))]
- #[inline]
- pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
- 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<I>
- where
- I: Iterator<Item = B> + Clone,
- B: Borrow<Item<'a>>,
- {
- 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](./format/strftime/index.html)
- /// on the supported escape sequences.
- #[cfg(feature = "unstable-locales")]
- #[inline]
- pub fn format_localized<'a>(
- &self,
- fmt: &'a str,
- locale: Locale,
- ) -> DelayedFormat<StrftimeItems<'a>> {
- self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale)
- }
-}
-
-impl<Tz: TimeZone> Datelike for DateTime<Tz> {
- #[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<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_year(year))
- }
-
- #[inline]
- fn with_month(&self, month: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_month(month))
- }
-
- #[inline]
- fn with_month0(&self, month0: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_month0(month0))
- }
-
- #[inline]
- fn with_day(&self, day: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_day(day))
- }
-
- #[inline]
- fn with_day0(&self, day0: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_day0(day0))
- }
-
- #[inline]
- fn with_ordinal(&self, ordinal: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_ordinal(ordinal))
- }
-
- #[inline]
- fn with_ordinal0(&self, ordinal0: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
- }
-}
-
-impl<Tz: TimeZone> Timelike for DateTime<Tz> {
- #[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<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_hour(hour))
- }
-
- #[inline]
- fn with_minute(&self, min: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_minute(min))
- }
-
- #[inline]
- fn with_second(&self, sec: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_second(sec))
- }
-
- #[inline]
- fn with_nanosecond(&self, nano: u32) -> Option<DateTime<Tz>> {
- map_local(self, |datetime| datetime.with_nanosecond(nano))
- }
-}
-
-// we need them as automatic impls cannot handle associated types
-impl<Tz: TimeZone> Copy for DateTime<Tz> where <Tz as TimeZone>::Offset: Copy {}
-unsafe impl<Tz: TimeZone> Send for DateTime<Tz> where <Tz as TimeZone>::Offset: Send {}
-
-impl<Tz: TimeZone, Tz2: TimeZone> PartialEq<DateTime<Tz2>> for DateTime<Tz> {
- fn eq(&self, other: &DateTime<Tz2>) -> bool {
- self.datetime == other.datetime
- }
-}
-
-impl<Tz: TimeZone> Eq for DateTime<Tz> {}
-
-impl<Tz: TimeZone, Tz2: TimeZone> PartialOrd<DateTime<Tz2>> for DateTime<Tz> {
- /// 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<Tz2>) -> Option<Ordering> {
- self.datetime.partial_cmp(&other.datetime)
- }
-}
-
-impl<Tz: TimeZone> Ord for DateTime<Tz> {
- fn cmp(&self, other: &DateTime<Tz>) -> Ordering {
- self.datetime.cmp(&other.datetime)
- }
-}
-
-impl<Tz: TimeZone> hash::Hash for DateTime<Tz> {
- fn hash<H: hash::Hasher>(&self, state: &mut H) {
- self.datetime.hash(state)
- }
-}
-
-impl<Tz: TimeZone> Add<OldDuration> for DateTime<Tz> {
- type Output = DateTime<Tz>;
-
- #[inline]
- fn add(self, rhs: OldDuration) -> DateTime<Tz> {
- self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed")
- }
-}
-
-impl<Tz: TimeZone> Sub<OldDuration> for DateTime<Tz> {
- type Output = DateTime<Tz>;
-
- #[inline]
- fn sub(self, rhs: OldDuration) -> DateTime<Tz> {
- self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed")
- }
-}
-
-impl<Tz: TimeZone> Sub<DateTime<Tz>> for DateTime<Tz> {
- type Output = OldDuration;
-
- #[inline]
- fn sub(self, rhs: DateTime<Tz>) -> OldDuration {
- self.signed_duration_since(rhs)
- }
-}
-
-impl<Tz: TimeZone> fmt::Debug for DateTime<Tz> {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{:?}{:?}", self.naive_local(), self.offset)
- }
-}
-
-impl<Tz: TimeZone> fmt::Display for DateTime<Tz>
-where
- Tz::Offset: fmt::Display,
-{
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{} {}", self.naive_local(), self.offset)
- }
-}
-
-impl str::FromStr for DateTime<Utc> {
- type Err = ParseError;
-
- fn from_str(s: &str) -> ParseResult<DateTime<Utc>> {
- s.parse::<DateTime<FixedOffset>>().map(|dt| dt.with_timezone(&Utc))
- }
-}
-
-#[cfg(feature = "clock")]
-impl str::FromStr for DateTime<Local> {
- type Err = ParseError;
-
- fn from_str(s: &str) -> ParseResult<DateTime<Local>> {
- s.parse::<DateTime<FixedOffset>>().map(|dt| dt.with_timezone(&Local))
- }
-}
-
-#[cfg(any(feature = "std", test))]
-impl From<SystemTime> for DateTime<Utc> {
- fn from(t: SystemTime) -> DateTime<Utc> {
- 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<SystemTime> for DateTime<Local> {
- fn from(t: SystemTime) -> DateTime<Local> {
- DateTime::<Utc>::from(t).with_timezone(&Local)
- }
-}
-
-#[cfg(any(feature = "std", test))]
-impl<Tz: TimeZone> From<DateTime<Tz>> for SystemTime {
- fn from(dt: DateTime<Tz>) -> 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<js_sys::Date> for DateTime<Utc> {
- fn from(date: js_sys::Date) -> DateTime<Utc> {
- DateTime::<Utc>::from(&date)
- }
-}
-
-#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
-impl From<&js_sys::Date> for DateTime<Utc> {
- fn from(date: &js_sys::Date) -> DateTime<Utc> {
- let millisecs_since_unix_epoch: u64 = date.get_time() as u64;
- let secs = millisecs_since_unix_epoch / 1000;
- let nanos = 1_000_000 * (millisecs_since_unix_epoch % 1000);
- let naive = NaiveDateTime::from_timestamp(secs as i64, nanos as u32);
- DateTime::from_utc(naive, Utc)
- }
-}
-
-#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
-impl From<DateTime<Utc>> for js_sys::Date {
- fn from(date: DateTime<Utc>) -> js_sys::Date {
- let js_date = js_sys::Date::new_0();
-
- js_date.set_utc_full_year_with_month_date(
- date.year() as u32,
- date.month0() as i32,
- date.day() as i32,
- );
-
- js_date.set_utc_hours(date.hour());
- js_date.set_utc_minutes(date.minute());
- js_date.set_utc_seconds(date.second());
-
- js_date
- }
-}
-
-#[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<Utc> = cdt_dt.into();
- assert_eq!(utc_dt, utc_dt2);
-}
-
-#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
-fn test_encodable_json<FUtc, FFixed, E>(to_string_utc: FUtc, to_string_fixed: FFixed)
-where
- FUtc: Fn(&DateTime<Utc>) -> Result<String, E>,
- FFixed: Fn(&DateTime<FixedOffset>) -> Result<String, E>,
- 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<FUtc, FFixed, FLocal, E>(
- utc_from_str: FUtc,
- fixed_from_str: FFixed,
- local_from_str: FLocal,
-) where
- FUtc: Fn(&str) -> Result<DateTime<Utc>, E>,
- FFixed: Fn(&str) -> Result<DateTime<FixedOffset>, E>,
- FLocal: Fn(&str) -> Result<DateTime<Local>, E>,
- E: ::core::fmt::Debug,
-{
- // should check against the offset as well (the normal DateTime comparison will ignore them)
- fn norm<Tz: TimeZone>(dt: &Option<DateTime<Tz>>) -> Option<(&DateTime<Tz>, &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<FUtc, FFixed, FLocal, E>(
- utc_from_str: FUtc,
- fixed_from_str: FFixed,
- local_from_str: FLocal,
-) where
- FUtc: Fn(&str) -> Result<rustc_serialize::TsSeconds<Utc>, E>,
- FFixed: Fn(&str) -> Result<rustc_serialize::TsSeconds<FixedOffset>, E>,
- FLocal: Fn(&str) -> Result<rustc_serialize::TsSeconds<Local>, E>,
- E: ::core::fmt::Debug,
-{
- fn norm<Tz: TimeZone>(dt: &Option<DateTime<Tz>>) -> Option<(&DateTime<Tz>, &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<Tz: TimeZone> Encodable for DateTime<Tz> {
- fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
- format!("{:?}", self).encode(s)
- }
- }
-
- // lik? function to convert a LocalResult into a serde-ish Result
- fn from<T, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
- 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<FixedOffset> {
- fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<FixedOffset>, D::Error> {
- d.read_str()?
- .parse::<DateTime<FixedOffset>>()
- .map_err(|_| d.error("invalid date and time"))
- }
- }
-
- #[allow(deprecated)]
- impl Decodable for TsSeconds<FixedOffset> {
- #[allow(deprecated)]
- fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<FixedOffset>, D::Error> {
- from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
- }
- }
-
- impl Decodable for DateTime<Utc> {
- fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Utc>, D::Error> {
- d.read_str()?
- .parse::<DateTime<FixedOffset>>()
- .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<Tz: TimeZone>(DateTime<Tz>);
-
- #[allow(deprecated)]
- impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
- /// Pull the inner DateTime<Tz> out
- #[allow(deprecated)]
- fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
- obj.0
- }
- }
-
- #[allow(deprecated)]
- impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
- type Target = DateTime<Tz>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
- }
-
- #[allow(deprecated)]
- impl Decodable for TsSeconds<Utc> {
- fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Utc>, D::Error> {
- from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
- }
- }
-
- #[cfg(feature = "clock")]
- impl Decodable for DateTime<Local> {
- fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Local>, D::Error> {
- match d.read_str()?.parse::<DateTime<FixedOffset>>() {
- Ok(dt) => Ok(dt.with_timezone(&Local)),
- Err(_) => Err(d.error("invalid date and time")),
- }
- }
- }
-
- #[cfg(feature = "clock")]
- #[allow(deprecated)]
- impl Decodable for TsSeconds<Local> {
- #[allow(deprecated)]
- fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Local>, 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 MilliSecondsTimestampVisitor;
-
- // lik? function to convert a LocalResult into a serde-ish Result
- fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
- 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),
- }
- }
-
- /// 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
- 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(d.deserialize_i64(NanoSecondsTimestampVisitor)?)
- }
-
- impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor {
- type Value = DateTime<Utc>;
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- write!(formatter, "a unix timestamp in nanoseconds")
- }
-
- /// Deserialize a timestamp in nanoseconds since the epoch
- fn visit_i64<E>(self, value: i64) -> Result<DateTime<Utc>, E>
- 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<E>(self, value: u64) -> Result<DateTime<Utc>, E>
- 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 optional 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
- 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(d.deserialize_option(OptionNanoSecondsTimestampVisitor)?)
- }
-
- struct OptionNanoSecondsTimestampVisitor;
-
- impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor {
- type Value = Option<DateTime<Utc>>;
-
- 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<D>(self, d: D) -> Result<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_none<E>(self) -> Result<Option<DateTime<Utc>>, E>
- where
- E: de::Error,
- {
- Ok(None)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_unit<E>(self) -> Result<Option<DateTime<Utc>>, E>
- 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
- 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc))?)
- }
-
- impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor {
- type Value = DateTime<Utc>;
-
- 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<E>(self, value: i64) -> Result<DateTime<Utc>, E>
- 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<E>(self, value: u64) -> Result<DateTime<Utc>, E>
- 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
- 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<T> {
- /// V(T),
- /// }
- ///
- /// #[derive(Deserialize, PartialEq, Debug)]
- /// struct S {
- /// #[serde(default, deserialize_with = "from_milli_tsopt")]
- /// time: Option<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<(), serde_json::Error> {
- /// let my_s: E<S> = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?;
- /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) }));
- /// let s: E<S> = serde_json::from_str(r#"{ "time": null }"#)?;
- /// assert_eq!(s, E::V(S { time: None }));
- /// let t: E<S> = 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<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(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<DateTime<Utc>>;
-
- 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<D>(self, d: D) -> Result<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_none<E>(self) -> Result<Option<DateTime<Utc>>, E>
- where
- E: de::Error,
- {
- Ok(None)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_unit<E>(self) -> Result<Option<DateTime<Utc>>, E>
- 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
- 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<Utc>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(d.deserialize_i64(SecondsTimestampVisitor)?)
- }
-
- impl<'de> de::Visitor<'de> for SecondsTimestampVisitor {
- type Value = DateTime<Utc>;
-
- 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<E>(self, value: i64) -> Result<DateTime<Utc>, E>
- where
- E: de::Error,
- {
- serde_from(Utc.timestamp_opt(value, 0), &value)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_u64<E>(self, value: u64) -> Result<DateTime<Utc>, E>
- 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<String, serde_json::Error> {
- /// 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<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
- 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<DateTime<Utc>>
- /// }
- ///
- /// # fn example() -> Result<S, serde_json::Error> {
- /// 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<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(d.deserialize_option(OptionSecondsTimestampVisitor)?)
- }
-
- struct OptionSecondsTimestampVisitor;
-
- impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor {
- type Value = Option<DateTime<Utc>>;
-
- 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<D>(self, d: D) -> Result<Option<DateTime<Utc>>, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- d.deserialize_i64(SecondsTimestampVisitor).map(Some)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_none<E>(self) -> Result<Option<DateTime<Utc>>, E>
- where
- E: de::Error,
- {
- Ok(None)
- }
-
- /// Deserialize a timestamp in seconds since the epoch
- fn visit_unit<E>(self) -> Result<Option<DateTime<Utc>>, E>
- where
- E: de::Error,
- {
- Ok(None)
- }
- }
- }
-
- impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
- /// Serialize into a rfc3339 time string
- ///
- /// See [the `serde` module](./serde/index.html) for alternate
- /// serializations.
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- 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<FixedOffset>;
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- write!(formatter, "a formatted date and time string or a unix timestamp")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<DateTime<FixedOffset>, E>
- where
- E: de::Error,
- {
- value.parse().map_err(|err: ::format::ParseError| E::custom(err))
- }
- }
-
- /// 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<FixedOffset> {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- 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<Utc> {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- 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<Local> {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local))
- }
- }
-
- #[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<Utc> = 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, 084_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::<Utc>::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::<DateTime<FixedOffset>>(),
- Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
- assert_eq!(
- "2015-02-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
- Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
- assert_eq!(
- "2015-02-18T23:16:9.15 UTC".parse::<DateTime<Utc>>(),
- Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
- assert_eq!(
- "2015-02-18T23:16:9.15UTC".parse::<DateTime<Utc>>(),
- Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
-
- assert_eq!(
- "2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
- 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::<DateTime<FixedOffset>>(),
- Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150))
- );
- assert!("2015-2-18T23:16:9.15".parse::<DateTime<FixedOffset>>().is_err());
-
- assert_eq!(
- "2015-2-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
- Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
- assert_eq!(
- "2015-2-18T13:16:9.15-10:00".parse::<DateTime<Utc>>(),
- Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))
- );
- assert!("2015-2-18T23:16:9.15".parse::<DateTime<Utc>>().is_err());
-
- // no test for `DateTime<Local>`, 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<Utc> = dt.to_string().parse().unwrap();
-
- let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600));
- let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
-
- let ndt_fixed = dt.with_timezone(&FixedOffset::east(0));
- let _dt: DateTime<FixedOffset> = 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<FixedOffset> = 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<Utc>
- assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
- assert_eq!(
- DateTime::<Utc>::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)),
- Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)
- );
- assert_eq!(
- DateTime::<Utc>::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)),
- Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)
- );
-
- // DateTime<Utc> -> 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<any tz> -> 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<Utc>
- assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
- assert_eq!(
- DateTime::<Utc>::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)),
- Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)
- );
- assert_eq!(
- DateTime::<Utc>::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)),
- Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)
- );
-
- // DateTime<Utc> -> 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<any tz> -> 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, 01, 02);
-
- // 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));
- }
-}
diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs
new file mode 100644
index 0000000..e4e460b
--- /dev/null
+++ b/src/datetime/mod.rs
@@ -0,0 +1,1841 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+//! ISO 8601 date and time with time zone.
+
+#[cfg(all(not(feature = "std"), feature = "alloc"))]
+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};
+
+#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
+use crate::format::Locale;
+use crate::format::{
+ parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed,
+ StrftimeItems, TOO_LONG,
+};
+#[cfg(feature = "alloc")]
+use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat};
+use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime};
+#[cfg(feature = "clock")]
+use crate::offset::Local;
+use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
+use crate::try_opt;
+#[allow(deprecated)]
+use crate::Date;
+use crate::{Datelike, Months, TimeDelta, Timelike, Weekday};
+
+#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
+use rkyv::{Archive, Deserialize, Serialize};
+
+#[cfg(feature = "rustc-serialize")]
+pub(super) mod rustc_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<Tz: TimeZone> {
+ datetime: NaiveDateTime,
+ offset: Tz::Offset,
+}
+
+/// The minimum possible `DateTime<Utc>`.
+#[deprecated(since = "0.4.20", note = "Use DateTime::MIN_UTC instead")]
+pub const MIN_DATETIME: DateTime<Utc> = DateTime::<Utc>::MIN_UTC;
+/// The maximum possible `DateTime<Utc>`.
+#[deprecated(since = "0.4.20", note = "Use DateTime::MAX_UTC instead")]
+pub const MAX_DATETIME: DateTime<Utc> = DateTime::<Utc>::MAX_UTC;
+
+impl<Tz: TimeZone> DateTime<Tz> {
+ /// 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_attr(not(feature = "clock"), doc = "```ignore")]
+ #[cfg_attr(feature = "clock", doc = "```rust")]
+ /// use chrono::{Local, DateTime};
+ ///
+ /// 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::<Local>::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<Tz> {
+ 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<Tz> {
+ 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<Tz> {
+ 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<Tz> {
+ 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> = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
+ /// let other: DateTime<FixedOffset> = 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 {
+ let local = self.naive_local();
+ NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap()
+ }
+
+ /// 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> = 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 {
+ self.datetime.timestamp()
+ }
+
+ /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{Utc, NaiveDate};
+ ///
+ /// 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 {
+ self.datetime.timestamp_millis()
+ }
+
+ /// Returns the number of non-leap-microseconds since January 1, 1970 UTC.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{Utc, NaiveDate};
+ ///
+ /// 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 {
+ self.datetime.timestamp_micros()
+ }
+
+ /// 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]
+ #[allow(deprecated)]
+ pub const fn timestamp_nanos(&self) -> i64 {
+ self.datetime.timestamp_nanos()
+ }
+
+ /// 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::{Utc, NaiveDate};
+ ///
+ /// 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<i64> {
+ self.datetime.timestamp_nanos_opt()
+ }
+
+ /// 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.datetime.timestamp_subsec_millis()
+ }
+
+ /// 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.datetime.timestamp_subsec_micros()
+ }
+
+ /// 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.timestamp_subsec_nanos()
+ }
+
+ /// 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<Tz2: TimeZone>(&self, tz: &Tz2) -> DateTime<Tz2> {
+ tz.from_utc_datetime(&self.datetime)
+ }
+
+ /// Fix the offset from UTC to its current value, dropping the associated timezone information.
+ /// This it useful for converting a generic `DateTime<Tz: Timezone>` to `DateTime<FixedOffset>`.
+ #[inline]
+ #[must_use]
+ pub fn fixed_offset(&self) -> DateTime<FixedOffset> {
+ self.with_timezone(&self.offset().fix())
+ }
+
+ /// Turn this `DateTime` into a `DateTime<Utc>`, dropping the offset and associated timezone
+ /// information.
+ #[inline]
+ #[must_use]
+ pub const fn to_utc(&self) -> DateTime<Utc> {
+ 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<DateTime<Tz>> {
+ 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 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.
+ #[must_use]
+ pub fn checked_add_months(self, rhs: Months) -> Option<DateTime<Tz>> {
+ self.naive_local()
+ .checked_add_months(rhs)?
+ .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<DateTime<Tz>> {
+ 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 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.
+ #[must_use]
+ pub fn checked_sub_months(self, rhs: Months) -> Option<DateTime<Tz>> {
+ self.naive_local()
+ .checked_sub_months(rhs)?
+ .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 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.
+ #[must_use]
+ pub fn checked_add_days(self, days: Days) -> Option<Self> {
+ self.naive_local()
+ .checked_add_days(days)?
+ .and_local_timezone(TimeZone::from_offset(&self.offset))
+ .single()
+ }
+
+ /// Subtract a duration in [`Days`] from the date part of the `DateTime`.
+ ///
+ /// # Errors
+ ///
+ /// Returns `None` 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.
+ #[must_use]
+ pub fn checked_sub_days(self, days: Days) -> Option<Self> {
+ self.naive_local()
+ .checked_sub_days(days)?
+ .and_local_timezone(TimeZone::from_offset(&self.offset))
+ .single()
+ }
+
+ /// 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<Tz2: TimeZone>(
+ self,
+ rhs: impl Borrow<DateTime<Tz2>>,
+ ) -> 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<u32> {
+ 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]
+ 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("writing rfc2822 datetime to string should never fail");
+ 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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap();
+ /// 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
+ }
+
+ /// The minimum possible `DateTime<Utc>`.
+ pub const MIN_UTC: DateTime<Utc> = DateTime { datetime: NaiveDateTime::MIN, offset: Utc };
+ /// The maximum possible `DateTime<Utc>`.
+ pub const MAX_UTC: DateTime<Utc> = DateTime { datetime: NaiveDateTime::MAX, offset: Utc };
+}
+
+impl DateTime<Utc> {
+ /// Makes a new [`DateTime<Utc>`] 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, Utc};
+ ///
+ /// let dt: DateTime<Utc> = DateTime::<Utc>::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<Self> {
+ Some(DateTime {
+ datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)),
+ offset: Utc,
+ })
+ }
+
+ /// Makes a new [`DateTime<Utc>`] 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 regard to [`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, Utc};
+ ///
+ /// let dt: DateTime<Utc> = DateTime::<Utc>::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<Self> {
+ Some(try_opt!(NaiveDateTime::from_timestamp_millis(millis)).and_utc())
+ }
+
+ /// The Unix Epoch, 1970-01-01 00:00:00 UTC.
+ pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc };
+}
+
+impl Default for DateTime<Utc> {
+ fn default() -> Self {
+ Utc.from_utc_datetime(&NaiveDateTime::default())
+ }
+}
+
+#[cfg(feature = "clock")]
+impl Default for DateTime<Local> {
+ fn default() -> Self {
+ Local.from_utc_datetime(&NaiveDateTime::default())
+ }
+}
+
+impl Default for DateTime<FixedOffset> {
+ fn default() -> Self {
+ FixedOffset::west_opt(0).unwrap().from_utc_datetime(&NaiveDateTime::default())
+ }
+}
+
+/// Convert a `DateTime<Utc>` instance into a `DateTime<FixedOffset>` instance.
+impl From<DateTime<Utc>> for DateTime<FixedOffset> {
+ /// Convert this `DateTime<Utc>` instance into a `DateTime<FixedOffset>` 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<Utc>) -> Self {
+ src.with_timezone(&FixedOffset::east_opt(0).unwrap())
+ }
+}
+
+/// Convert a `DateTime<Utc>` instance into a `DateTime<Local>` instance.
+#[cfg(feature = "clock")]
+impl From<DateTime<Utc>> for DateTime<Local> {
+ /// Convert this `DateTime<Utc>` instance into a `DateTime<Local>` instance.
+ ///
+ /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones.
+ fn from(src: DateTime<Utc>) -> Self {
+ src.with_timezone(&Local)
+ }
+}
+
+/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
+impl From<DateTime<FixedOffset>> for DateTime<Utc> {
+ /// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
+ ///
+ /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone
+ /// difference.
+ fn from(src: DateTime<FixedOffset>) -> Self {
+ src.with_timezone(&Utc)
+ }
+}
+
+/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
+#[cfg(feature = "clock")]
+impl From<DateTime<FixedOffset>> for DateTime<Local> {
+ /// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
+ ///
+ /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local
+ /// time.
+ fn from(src: DateTime<FixedOffset>) -> Self {
+ src.with_timezone(&Local)
+ }
+}
+
+/// Convert a `DateTime<Local>` instance into a `DateTime<Utc>` instance.
+#[cfg(feature = "clock")]
+impl From<DateTime<Local>> for DateTime<Utc> {
+ /// Convert this `DateTime<Local>` instance into a `DateTime<Utc>` instance.
+ ///
+ /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in
+ /// timezones.
+ fn from(src: DateTime<Local>) -> Self {
+ src.with_timezone(&Utc)
+ }
+}
+
+/// Convert a `DateTime<Local>` instance into a `DateTime<FixedOffset>` instance.
+#[cfg(feature = "clock")]
+impl From<DateTime<Local>> for DateTime<FixedOffset> {
+ /// Convert this `DateTime<Local>` instance into a `DateTime<FixedOffset>` instance.
+ ///
+ /// Conversion is performed via [`DateTime::with_timezone`].
+ fn from(src: DateTime<Local>) -> Self {
+ src.with_timezone(&src.offset().fix())
+ }
+}
+
+/// Maps the local datetime to other datetime with given conversion function.
+fn map_local<Tz: TimeZone, F>(dt: &DateTime<Tz>, mut f: F) -> Option<DateTime<Tz>>
+where
+ F: FnMut(NaiveDateTime) -> Option<NaiveDateTime>,
+{
+ f(dt.overflowing_naive_local())
+ .and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single())
+ .filter(|dt| dt >= &DateTime::<Utc>::MIN_UTC && dt <= &DateTime::<Utc>::MAX_UTC)
+}
+
+impl DateTime<FixedOffset> {
+ /// Parses an RFC 2822 date-and-time string into a `DateTime<FixedOffset>` 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 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<DateTime<FixedOffset>> {
+ 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<FixedOffset>` 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<DateTime<FixedOffset>> {
+ let mut parsed = Parsed::new();
+ let (s, _) = parse_rfc3339(&mut parsed, s)?;
+ if !s.is_empty() {
+ return Err(TOO_LONG);
+ }
+ parsed.to_datetime()
+ }
+
+ /// Parses a string from a user-specified format into a `DateTime<FixedOffset>` 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](./format/strftime/index.html) for supported format
+ /// sequences.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate};
+ ///
+ /// 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<DateTime<FixedOffset>> {
+ 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<FixedOffset>` 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<FixedOffset>, &'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<Tz: TimeZone> DateTime<Tz>
+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<I>
+ where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+ {
+ 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> = 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<StrftimeItems<'a>> {
+ 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<I>
+ where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+ {
+ 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<StrftimeItems<'a>> {
+ self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale)
+ }
+}
+
+impl<Tz: TimeZone> Datelike for DateTime<Tz> {
+ #[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.
+ /// - When the `NaiveDateTime` 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.
+ fn with_year(&self, year: i32) -> Option<DateTime<Tz>> {
+ map_local(self, |datetime| datetime.with_year(year))
+ }
+
+ /// Makes a new `DateTime` with the month number (starting from 1) changed.
+ ///
+ /// See also the [`NaiveDate::with_month`] method.
+ ///
+ /// # Errors
+ ///
+ /// Returns `None` if:
+ /// - The resulting date does not exist.
+ /// - 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<DateTime<Tz>> {
+ 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.
+ /// - 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<DateTime<Tz>> {
+ 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.
+ /// - 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<DateTime<Tz>> {
+ 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.
+ /// - 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<DateTime<Tz>> {
+ 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.
+ /// - 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<DateTime<Tz>> {
+ 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.
+ /// - 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<DateTime<Tz>> {
+ map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
+ }
+}
+
+impl<Tz: TimeZone> Timelike for DateTime<Tz> {
+ #[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<DateTime<Tz>> {
+ 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<DateTime<Tz>> {
+ 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<DateTime<Tz>> {
+ 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<DateTime<Tz>> {
+ map_local(self, |datetime| datetime.with_nanosecond(nano))
+ }
+}
+
+// we need them as automatic impls cannot handle associated types
+impl<Tz: TimeZone> Copy for DateTime<Tz> where <Tz as TimeZone>::Offset: Copy {}
+unsafe impl<Tz: TimeZone> Send for DateTime<Tz> where <Tz as TimeZone>::Offset: Send {}
+
+impl<Tz: TimeZone, Tz2: TimeZone> PartialEq<DateTime<Tz2>> for DateTime<Tz> {
+ fn eq(&self, other: &DateTime<Tz2>) -> bool {
+ self.datetime == other.datetime
+ }
+}
+
+impl<Tz: TimeZone> Eq for DateTime<Tz> {}
+
+impl<Tz: TimeZone, Tz2: TimeZone> PartialOrd<DateTime<Tz2>> for DateTime<Tz> {
+ /// 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<Tz2>) -> Option<Ordering> {
+ self.datetime.partial_cmp(&other.datetime)
+ }
+}
+
+impl<Tz: TimeZone> Ord for DateTime<Tz> {
+ fn cmp(&self, other: &DateTime<Tz>) -> Ordering {
+ self.datetime.cmp(&other.datetime)
+ }
+}
+
+impl<Tz: TimeZone> hash::Hash for DateTime<Tz> {
+ fn hash<H: hash::Hasher>(&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<Tz>::checked_add_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> Add<TimeDelta> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn add(self, rhs: TimeDelta) -> DateTime<Tz> {
+ 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<Tz>::checked_add_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> Add<Duration> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn add(self, rhs: Duration) -> DateTime<Tz> {
+ 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<Tz>::checked_add_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> AddAssign<TimeDelta> for DateTime<Tz> {
+ #[inline]
+ 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<Tz>::checked_add_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> AddAssign<Duration> for DateTime<Tz> {
+ #[inline]
+ 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<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn add(mut self, rhs: FixedOffset) -> DateTime<Tz> {
+ 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<Tz>::checked_add_months`] to get an `Option` instead.
+impl<Tz: TimeZone> Add<Months> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ 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<Tz>::checked_sub_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> Sub<TimeDelta> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn sub(self, rhs: TimeDelta) -> DateTime<Tz> {
+ 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<Tz>::checked_sub_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> Sub<Duration> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn sub(self, rhs: Duration) -> DateTime<Tz> {
+ 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<Tz>::checked_sub_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> SubAssign<TimeDelta> for DateTime<Tz> {
+ #[inline]
+ 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<Tz>::checked_sub_signed`] to get an `Option` instead.
+impl<Tz: TimeZone> SubAssign<Duration> for DateTime<Tz> {
+ #[inline]
+ 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<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ #[inline]
+ fn sub(mut self, rhs: FixedOffset) -> DateTime<Tz> {
+ 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<Tz>::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<Tz>::checked_sub_months`] to get an `Option` instead.
+impl<Tz: TimeZone> Sub<Months> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ fn sub(self, rhs: Months) -> Self::Output {
+ self.checked_sub_months(rhs).expect("`DateTime - Months` out of range")
+ }
+}
+
+impl<Tz: TimeZone> Sub<DateTime<Tz>> for DateTime<Tz> {
+ type Output = TimeDelta;
+
+ #[inline]
+ fn sub(self, rhs: DateTime<Tz>) -> TimeDelta {
+ self.signed_duration_since(rhs)
+ }
+}
+
+impl<Tz: TimeZone> Sub<&DateTime<Tz>> for DateTime<Tz> {
+ type Output = TimeDelta;
+
+ #[inline]
+ fn sub(self, rhs: &DateTime<Tz>) -> 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<Tz>::checked_sub_days` to get an `Option` instead.
+impl<Tz: TimeZone> Add<Days> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ 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<Tz>::checked_sub_days` to get an `Option` instead.
+impl<Tz: TimeZone> Sub<Days> for DateTime<Tz> {
+ type Output = DateTime<Tz>;
+
+ fn sub(self, days: Days) -> Self::Output {
+ self.checked_sub_days(days).expect("`DateTime - Days` out of range")
+ }
+}
+
+impl<Tz: TimeZone> fmt::Debug for DateTime<Tz> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.overflowing_naive_local().fmt(f)?;
+ self.offset.fmt(f)
+ }
+}
+
+// `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 `<<Tz as offset::TimeZone>::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<Tz: TimeZone> fmt::Debug for ArchivedDateTime<Tz>
+where
+ Tz: Archive,
+ <Tz as Archive>::Archived: fmt::Debug,
+ <<Tz as TimeZone>::Offset as Archive>::Archived: fmt::Debug,
+ <Tz as TimeZone>::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<Tz: TimeZone> fmt::Display for DateTime<Tz>
+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::<DateTime<Utc>>()?;
+/// "2012-12-12 12:12:12Z".parse::<DateTime<Utc>>()?;
+/// "2012-12-12 12:12:12+0000".parse::<DateTime<Utc>>()?;
+/// "2012-12-12 12:12:12+00:00".parse::<DateTime<Utc>>()?;
+/// # Ok::<(), chrono::ParseError>(())
+/// ```
+impl str::FromStr for DateTime<Utc> {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> ParseResult<DateTime<Utc>> {
+ s.parse::<DateTime<FixedOffset>>().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::<DateTime<Local>>()?;
+/// "2012-12-12 12:12:12Z".parse::<DateTime<Local>>()?;
+/// "2012-12-12 12:12:12+0000".parse::<DateTime<Local>>()?;
+/// "2012-12-12 12:12:12+00:00".parse::<DateTime<Local>>()?;
+/// # Ok::<(), chrono::ParseError>(())
+/// ```
+#[cfg(feature = "clock")]
+impl str::FromStr for DateTime<Local> {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> ParseResult<DateTime<Local>> {
+ s.parse::<DateTime<FixedOffset>>().map(|dt| dt.with_timezone(&Local))
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<SystemTime> for DateTime<Utc> {
+ fn from(t: SystemTime) -> DateTime<Utc> {
+ 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<SystemTime> for DateTime<Local> {
+ fn from(t: SystemTime) -> DateTime<Local> {
+ DateTime::<Utc>::from(t).with_timezone(&Local)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<Tz: TimeZone> From<DateTime<Tz>> for SystemTime {
+ fn from(dt: DateTime<Tz>) -> 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_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+))]
+impl From<js_sys::Date> for DateTime<Utc> {
+ fn from(date: js_sys::Date) -> DateTime<Utc> {
+ DateTime::<Utc>::from(&date)
+ }
+}
+
+#[cfg(all(
+ target_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+))]
+impl From<&js_sys::Date> for DateTime<Utc> {
+ fn from(date: &js_sys::Date) -> DateTime<Utc> {
+ Utc.timestamp_millis_opt(date.get_time() as i64).unwrap()
+ }
+}
+
+#[cfg(all(
+ target_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+))]
+impl From<DateTime<Utc>> for js_sys::Date {
+ /// Converts a `DateTime<Utc>` 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<Utc>) -> 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<Tz>, due to
+// the nontrivial bound <Tz as TimeZone>::Offset: Arbitrary.
+#[cfg(all(feature = "arbitrary", feature = "std"))]
+impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime<Tz>
+where
+ Tz: TimeZone,
+ <Tz as TimeZone>::Offset: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<DateTime<Tz>> {
+ let datetime = NaiveDateTime::arbitrary(u)?;
+ let offset = <Tz as TimeZone>::Offset::arbitrary(u)?;
+ Ok(DateTime::from_naive_utc_and_offset(datetime, offset))
+ }
+}
+
+#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
+fn test_encodable_json<FUtc, FFixed, E>(to_string_utc: FUtc, to_string_fixed: FFixed)
+where
+ FUtc: Fn(&DateTime<Utc>) -> Result<String, E>,
+ FFixed: Fn(&DateTime<FixedOffset>) -> Result<String, E>,
+ E: ::core::fmt::Debug,
+{
+ assert_eq!(
+ to_string_utc(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(),
+ Some(r#""2014-07-24T12:34:06Z""#.into())
+ );
+
+ assert_eq!(
+ to_string_fixed(
+ &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""#.into())
+ );
+ assert_eq!(
+ to_string_fixed(
+ &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""#.into())
+ );
+}
+
+#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))]
+fn test_decodable_json<FUtc, FFixed, FLocal, E>(
+ utc_from_str: FUtc,
+ fixed_from_str: FFixed,
+ local_from_str: FLocal,
+) where
+ FUtc: Fn(&str) -> Result<DateTime<Utc>, E>,
+ FFixed: Fn(&str) -> Result<DateTime<FixedOffset>, E>,
+ FLocal: Fn(&str) -> Result<DateTime<Local>, E>,
+ E: ::core::fmt::Debug,
+{
+ // should check against the offset as well (the normal DateTime comparison will ignore them)
+ fn norm<Tz: TimeZone>(dt: &Option<DateTime<Tz>>) -> Option<(&DateTime<Tz>, &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.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))
+ );
+ assert_eq!(
+ norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
+ norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))
+ );
+
+ assert_eq!(
+ norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()),
+ norm(&Some(
+ FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()
+ ))
+ );
+ assert_eq!(
+ norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
+ 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
+ assert_eq!(
+ local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"),
+ Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()
+ );
+ assert_eq!(
+ local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"),
+ Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()
+ );
+
+ 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<FUtc, FFixed, FLocal, E>(
+ utc_from_str: FUtc,
+ fixed_from_str: FFixed,
+ local_from_str: FLocal,
+) where
+ FUtc: Fn(&str) -> Result<rustc_serialize::TsSeconds<Utc>, E>,
+ FFixed: Fn(&str) -> Result<rustc_serialize::TsSeconds<FixedOffset>, E>,
+ FLocal: Fn(&str) -> Result<rustc_serialize::TsSeconds<Local>, E>,
+ E: ::core::fmt::Debug,
+{
+ fn norm<Tz: TimeZone>(dt: &Option<DateTime<Tz>>) -> Option<(&DateTime<Tz>, &Tz::Offset)> {
+ dt.as_ref().map(|dt| (dt, dt.offset()))
+ }
+
+ assert_eq!(
+ norm(&utc_from_str("0").ok().map(DateTime::from)),
+ norm(&Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()))
+ );
+ assert_eq!(
+ norm(&utc_from_str("-1").ok().map(DateTime::from)),
+ norm(&Some(Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap()))
+ );
+
+ assert_eq!(
+ norm(&fixed_from_str("0").ok().map(DateTime::from)),
+ norm(&Some(
+ FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()
+ ))
+ );
+ assert_eq!(
+ norm(&fixed_from_str("-1").ok().map(DateTime::from)),
+ norm(&Some(
+ FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap()
+ ))
+ );
+
+ assert_eq!(
+ *fixed_from_str("0").expect("0 timestamp should parse"),
+ Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()
+ );
+ assert_eq!(
+ *local_from_str("-1").expect("-1 timestamp should parse"),
+ Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap()
+ );
+}
diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs
new file mode 100644
index 0000000..f704b2f
--- /dev/null
+++ b/src/datetime/rustc_serialize.rs
@@ -0,0 +1,124 @@
+use super::DateTime;
+use crate::format::SecondsFormat;
+#[cfg(feature = "clock")]
+use crate::offset::Local;
+use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc};
+use core::fmt;
+use core::ops::Deref;
+use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
+
+impl<Tz: TimeZone> Encodable for DateTime<Tz> {
+ fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
+ self.to_rfc3339_opts(SecondsFormat::AutoSi, true).encode(s)
+ }
+}
+
+// lik? function to convert a LocalResult into a serde-ish Result
+fn from<T, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
+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<FixedOffset> {
+ fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<FixedOffset>, D::Error> {
+ d.read_str()?.parse::<DateTime<FixedOffset>>().map_err(|_| d.error("invalid date and time"))
+ }
+}
+
+#[allow(deprecated)]
+impl Decodable for TsSeconds<FixedOffset> {
+ #[allow(deprecated)]
+ fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<FixedOffset>, D::Error> {
+ from(FixedOffset::east_opt(0).unwrap().timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
+ }
+}
+
+impl Decodable for DateTime<Utc> {
+ fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Utc>, D::Error> {
+ d.read_str()?
+ .parse::<DateTime<FixedOffset>>()
+ .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<Tz: TimeZone>(DateTime<Tz>);
+
+#[allow(deprecated)]
+impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
+ /// Pull the inner `DateTime<Tz>` out
+ #[allow(deprecated)]
+ fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
+ obj.0
+ }
+}
+
+#[allow(deprecated)]
+impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
+ type Target = DateTime<Tz>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+#[allow(deprecated)]
+impl Decodable for TsSeconds<Utc> {
+ fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Utc>, D::Error> {
+ from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
+ }
+}
+
+#[cfg(feature = "clock")]
+impl Decodable for DateTime<Local> {
+ fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Local>, D::Error> {
+ match d.read_str()?.parse::<DateTime<FixedOffset>>() {
+ Ok(dt) => Ok(dt.with_timezone(&Local)),
+ Err(_) => Err(d.error("invalid date and time")),
+ }
+ }
+}
+
+#[cfg(feature = "clock")]
+#[allow(deprecated)]
+impl Decodable for TsSeconds<Local> {
+ #[allow(deprecated)]
+ fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Local>, D::Error> {
+ from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(|dt| TsSeconds(dt.with_timezone(&Local)))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::datetime::test_encodable_json;
+ use crate::datetime::{test_decodable_json, test_decodable_json_timestamps};
+ use rustc_serialize::json;
+
+ #[test]
+ fn test_encodable() {
+ test_encodable_json(json::encode, json::encode);
+ }
+
+ #[cfg(feature = "clock")]
+ #[test]
+ fn test_decodable() {
+ test_decodable_json(json::decode, json::decode, json::decode);
+ }
+
+ #[cfg(feature = "clock")]
+ #[test]
+ fn test_decodable_timestamps() {
+ test_decodable_json_timestamps(json::decode, json::decode, json::decode);
+ }
+}
diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs
new file mode 100644
index 0000000..dcdb36d
--- /dev/null
+++ b/src/datetime/serde.rs
@@ -0,0 +1,1256 @@
+use core::fmt;
+use serde::{de, ser};
+
+use super::DateTime;
+use crate::format::{write_rfc3339, SecondsFormat};
+use crate::naive::datetime::serde::serde_from;
+#[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 into an ISO 8601 formatted string.
+///
+/// See [the `serde` module](./serde/index.html) for alternate
+/// serializations.
+impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ struct FormatIso8601<'a, Tz: TimeZone> {
+ inner: &'a DateTime<Tz>,
+ }
+
+ impl<'a, Tz: TimeZone> fmt::Display for FormatIso8601<'a, 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> de::Visitor<'de> for DateTimeVisitor {
+ type Value = DateTime<FixedOffset>;
+
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ 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<FixedOffset> {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ 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<Utc> {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ 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<Local> {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ 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<Utc>
+/// }
+///
+/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).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::offset::TimeZone;
+ use crate::{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.
+ ///
+ /// # 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<Utc>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap(),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Utc>
+ /// }
+ ///
+ /// 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<DateTime<Utc>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(NanoSecondsTimestampVisitor)
+ }
+
+ impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor {
+ type Value = DateTime<Utc>;
+
+ 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<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ serde_from(
+ Utc.timestamp_opt(
+ value.div_euclid(1_000_000_000),
+ (value.rem_euclid(1_000_000_000)) as u32,
+ ),
+ &value,
+ )
+ }
+
+ /// Deserialize a timestamp in nanoseconds since the epoch
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ 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 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<DateTime<Utc>>
+/// }
+///
+/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).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 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<DateTime<Utc>>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap()),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<DateTime<Utc>>
+ /// }
+ ///
+ /// 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<Option<DateTime<Utc>>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionNanoSecondsTimestampVisitor)
+ }
+
+ struct OptionNanoSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor {
+ type Value = Option<DateTime<Utc>>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in nanoseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in nanoseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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<Utc>
+/// }
+///
+/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).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 super::{serde_from, MicroSecondsTimestampVisitor};
+ use crate::offset::TimeZone;
+ use crate::{DateTime, Utc};
+
+ /// 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<Utc>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap(),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Utc>
+ /// }
+ ///
+ /// 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<DateTime<Utc>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MicroSecondsTimestampVisitor)
+ }
+
+ impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor {
+ type Value = DateTime<Utc>;
+
+ 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<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ serde_from(
+ Utc.timestamp_opt(
+ value.div_euclid(1_000_000),
+ (value.rem_euclid(1_000_000) * 1_000) as u32,
+ ),
+ &value,
+ )
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ 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
+/// # 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<DateTime<Utc>>
+/// }
+///
+/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).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::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<DateTime<Utc>>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap()),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<DateTime<Utc>>
+ /// }
+ ///
+ /// 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<Option<DateTime<Utc>>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionMicroSecondsTimestampVisitor)
+ }
+
+ struct OptionMicroSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor {
+ type Value = Option<DateTime<Utc>>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in microseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in microseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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<Utc>
+/// }
+///
+/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).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 super::{serde_from, MilliSecondsTimestampVisitor};
+ use crate::offset::TimeZone;
+ use crate::{DateTime, Utc};
+
+ /// 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<Utc>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap(),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Utc>
+ /// }
+ ///
+ /// 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<DateTime<Utc>, 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<Utc>;
+
+ 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<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ serde_from(Utc.timestamp_millis_opt(value), &value)
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ 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
+/// # 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<DateTime<Utc>>
+/// }
+///
+/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).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::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<DateTime<Utc>>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap()),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699918}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<T> {
+ /// V(T),
+ /// }
+ ///
+ /// #[derive(Deserialize, PartialEq, Debug)]
+ /// struct S {
+ /// #[serde(default, deserialize_with = "from_milli_tsopt")]
+ /// time: Option<DateTime<Utc>>
+ /// }
+ ///
+ /// let my_s: E<S> = 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<S> = serde_json::from_str(r#"{ "time": null }"#)?;
+ /// assert_eq!(s, E::V(S { time: None }));
+ /// let t: E<S> = 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<Option<DateTime<Utc>>, 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<DateTime<Utc>>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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<Utc>
+/// }
+///
+/// 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 super::{serde_from, SecondsTimestampVisitor};
+ use crate::{DateTime, LocalResult, TimeZone, Utc};
+
+ /// 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<Utc>
+ /// }
+ ///
+ /// 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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Utc>
+ /// }
+ ///
+ /// 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<DateTime<Utc>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(SecondsTimestampVisitor)
+ }
+
+ impl<'de> de::Visitor<'de> for SecondsTimestampVisitor {
+ type Value = DateTime<Utc>;
+
+ 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<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ serde_from(Utc.timestamp_opt(value, 0), &value)
+ }
+
+ /// Deserialize a timestamp in seconds since the epoch
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ serde_from(
+ if value > i64::MAX as u64 {
+ LocalResult::None
+ } else {
+ 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
+/// # 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<DateTime<Utc>>
+/// }
+///
+/// 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<DateTime<Utc>>
+ /// }
+ ///
+ /// 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<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+ 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<DateTime<Utc>>
+ /// }
+ ///
+ /// 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<Option<DateTime<Utc>>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionSecondsTimestampVisitor)
+ }
+
+ struct OptionSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor {
+ type Value = Option<DateTime<Utc>>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(SecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in seconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in seconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[cfg(feature = "clock")]
+ use crate::datetime::test_decodable_json;
+ use crate::datetime::test_encodable_json;
+ use crate::{DateTime, FixedOffset, TimeZone, Utc};
+ use core::fmt;
+
+ #[test]
+ fn test_serde_serialize() {
+ test_encodable_json(serde_json::to_string, serde_json::to_string);
+ }
+
+ #[cfg(feature = "clock")]
+ #[test]
+ fn test_serde_deserialize() {
+ test_decodable_json(
+ |input| serde_json::from_str(input),
+ |input| serde_json::from_str(input),
+ |input| 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 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<Utc> = deserialize(&encoded).unwrap();
+ assert_eq!(dt, decoded);
+ assert_eq!(dt.offset(), decoded.offset());
+ }
+
+ #[test]
+ fn test_serde_no_offset_debug() {
+ use crate::{LocalResult, 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) -> LocalResult<TestTimeZone> {
+ LocalResult::Single(TestTimeZone)
+ }
+ fn offset_from_local_datetime(
+ &self,
+ _local: &NaiveDateTime,
+ ) -> LocalResult<TestTimeZone> {
+ LocalResult::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<FixedOffset> = 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 0000000..be83955
--- /dev/null
+++ b/src/datetime/tests.rs
@@ -0,0 +1,1638 @@
+use super::DateTime;
+use crate::naive::{NaiveDate, NaiveTime};
+use crate::offset::{FixedOffset, TimeZone, Utc};
+#[cfg(feature = "clock")]
+use crate::offset::{Local, Offset};
+use crate::{Datelike, Days, LocalResult, 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::LocalResult<Self::Offset> {
+ unimplemented!()
+ }
+
+ fn offset_from_local_datetime(
+ &self,
+ local: &NaiveDateTime,
+ ) -> crate::LocalResult<Self::Offset> {
+ 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::hours(1));
+
+ 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::hours(1));
+
+ if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end {
+ LocalResult::Single(DstTester::summer_offset())
+ } else if *local >= local_to_winter_transition_start
+ && *local < local_to_summer_transition_start
+ {
+ LocalResult::Single(DstTester::winter_offset())
+ } else if *local >= local_to_winter_transition_end
+ && *local < local_to_winter_transition_start
+ {
+ LocalResult::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset())
+ } else if *local >= local_to_summer_transition_start
+ && *local < local_to_summer_transition_end
+ {
+ LocalResult::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_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<FixedOffset>
+#[allow(clippy::too_many_arguments)]
+fn ymdhms(
+ fixedoffset: &FixedOffset,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ min: u32,
+ sec: u32,
+) -> DateTime<FixedOffset> {
+ fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap()
+}
+
+// local helper function to easily create a DateTime<FixedOffset>
+#[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> {
+ 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<FixedOffset>
+#[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> {
+ 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<FixedOffset>
+#[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> {
+ fixedoffset
+ .with_ymd_and_hms(year, month, day, hour, min, sec)
+ .unwrap()
+ .with_nanosecond(nano)
+ .unwrap()
+}
+
+// local helper function to easily create a DateTime<Utc>
+#[cfg(feature = "alloc")]
+fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime<Utc> {
+ Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap()
+}
+
+// local helper function to easily create a DateTime<Utc>
+fn ymdhms_milli_utc(
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ min: u32,
+ sec: u32,
+ milli: u32,
+) -> DateTime<Utc> {
+ 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::seconds(3600 + 60 + 1),
+ 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::seconds(-7 * 3600 - 3 * 60 - 3)
+ );
+
+ 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 substract 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::<DateTime<FixedOffset>>(),
+ 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::<DateTime<Utc>>(),
+ 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::<DateTime<Utc>>(),
+ 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::<DateTime<Utc>>(),
+ 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::<DateTime<Utc>>(),
+ 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::<DateTime<FixedOffset>>(),
+ 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::<DateTime<FixedOffset>>(),
+ 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::<DateTime<FixedOffset>>().is_err());
+
+ assert_eq!(
+ "2015-2-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
+ 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::<DateTime<Utc>>(),
+ 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::<DateTime<Utc>>().is_err());
+ assert!("2015-02-18T23:16:9.15øøø".parse::<DateTime<Utc>>().is_err());
+
+ // no test for `DateTime<Local>`, 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::<DateTime<Utc>>() {
+ 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::<DateTime<Utc>>() {
+ 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
+ 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::<DateTime<Utc>>().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(),
+ NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset()
+ );
+
+ assert_eq!(
+ "2015-02-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
+ Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150))
+ );
+ assert_eq!(
+ "2015-02-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
+ Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)),
+ );
+ assert_eq!(
+ "2015-02-18T23:16:9.15 UTC".parse::<DateTime<Utc>>(),
+ Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150))
+ );
+ assert_eq!(
+ "2015-02-18T23:16:9.15UTC".parse::<DateTime<Utc>>(),
+ Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150))
+ );
+
+ assert_eq!(
+ "2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
+ Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150))
+ );
+ assert_eq!(
+ "2015-2-18T13:16:9.15-10:00".parse::<DateTime<FixedOffset>>(),
+ Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150))
+ );
+ assert!("2015-2-18T23:16:9.15".parse::<DateTime<FixedOffset>>().is_err());
+
+ assert_eq!(
+ "2015-2-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
+ Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150))
+ );
+ assert_eq!(
+ "2015-2-18T13:16:9.15-10:00".parse::<DateTime<Utc>>(),
+ Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150))
+ );
+ assert!("2015-2-18T23:16:9.15".parse::<DateTime<Utc>>().is_err());
+
+ // no test for `DateTime<Local>`, 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<Utc> = dt.to_string().parse().unwrap();
+
+ let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap());
+ let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
+
+ let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap());
+ let _dt: DateTime<FixedOffset> = 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<FixedOffset> = 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() {
+ fn _assert_send_copy<T: Send + Copy>() {}
+ // UTC is known to be `Send + Copy`.
+ _assert_send_copy::<DateTime<Utc>>();
+}
+
+#[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_arch = "wasm32", 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<Utc>
+ assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
+ assert_eq!(
+ DateTime::<Utc>::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::<Utc>::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<Utc> -> 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<any tz> -> 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::<Utc>::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::<FixedOffset>::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::<FixedOffset>::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]
+fn test_datetime_from_timestamp_millis() {
+ // 2000-01-12T01:02:03:004Z
+ let naive_dt =
+ NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_milli_opt(1, 2, 3, 4).unwrap();
+ let datetime_utc = DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc);
+ assert_eq!(
+ datetime_utc,
+ DateTime::<Utc>::from_timestamp_millis(datetime_utc.timestamp_millis()).unwrap()
+ );
+}
+
+#[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() {
+ 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::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
+ // A bit more than 2 years.
+ let two_year_ago =
+ Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64);
+
+ 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() + TimeDelta::weeks(12);
+ 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::seconds(60);
+ assert_eq!(datetime_add, datetime + TimeDelta::seconds(60));
+
+ 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::seconds(60));
+
+ 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::seconds(60));
+}
+
+#[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::days(1);
+ assert_eq!(datetime_add, datetime + TimeDelta::days(i))
+ }
+}
+
+#[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::minutes(90);
+ assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90));
+
+ 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::minutes(90));
+
+ 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::minutes(90));
+}
+
+#[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_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::hours(1)));
+ 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_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::hours(1)));
+ 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]
+#[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::days(1);
+ assert_eq!(datetime_sub, datetime - TimeDelta::days(i))
+ }
+}
+
+#[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<FixedOffset> = 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<Utc> = 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<Utc> = 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::<Local>::from_local(naive, offset), now);
+ assert_eq!(DateTime::<Local>::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 <https://github.com/chronotope/chrono/issues/1289>.
+#[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 64b8e4b..0000000
--- 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 0000000..9b05528
--- /dev/null
+++ b/src/format/formatting.rs
@@ -0,0 +1,954 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+//! Date and time formatting routines.
+
+#[cfg(all(not(feature = "std"), feature = "alloc"))]
+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", feature = "rustc-serialize"))]
+use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike};
+#[cfg(feature = "alloc")]
+use crate::{NaiveDate, NaiveTime, Weekday};
+
+#[cfg(feature = "alloc")]
+use super::locales;
+#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
+use super::Locale;
+#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))]
+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<I> {
+ /// The date view, if any.
+ date: Option<NaiveDate>,
+ /// The time view, if any.
+ time: Option<NaiveTime>,
+ /// 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.
+ #[cfg(feature = "unstable-locales")]
+ locale: Option<Locale>,
+}
+
+#[cfg(feature = "alloc")]
+impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
+ /// Makes a new `DelayedFormat` value out of local date and time.
+ #[must_use]
+ pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
+ DelayedFormat {
+ date,
+ time,
+ off: None,
+ items,
+ #[cfg(feature = "unstable-locales")]
+ locale: None,
+ }
+ }
+
+ /// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
+ #[must_use]
+ pub fn new_with_offset<Off>(
+ date: Option<NaiveDate>,
+ time: Option<NaiveTime>,
+ offset: &Off,
+ items: I,
+ ) -> DelayedFormat<I>
+ where
+ Off: Offset + Display,
+ {
+ let name_and_diff = (offset.to_string(), offset.fix());
+ DelayedFormat {
+ date,
+ time,
+ off: Some(name_and_diff),
+ items,
+ #[cfg(feature = "unstable-locales")]
+ locale: None,
+ }
+ }
+
+ /// 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<NaiveDate>,
+ time: Option<NaiveTime>,
+ items: I,
+ locale: Locale,
+ ) -> DelayedFormat<I> {
+ DelayedFormat { date, time, off: None, items, locale: Some(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<Off>(
+ date: Option<NaiveDate>,
+ time: Option<NaiveTime>,
+ offset: &Off,
+ items: I,
+ locale: Locale,
+ ) -> DelayedFormat<I>
+ where
+ Off: Offset + Display,
+ {
+ let name_and_diff = (offset.to_string(), offset.fix());
+ DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) }
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[cfg(feature = "unstable-locales")]
+ let locale = self.locale;
+ #[cfg(not(feature = "unstable-locales"))]
+ let locale = None;
+
+ let mut result = String::new();
+ for item in self.items.clone() {
+ format_inner(
+ &mut result,
+ self.date.as_ref(),
+ self.time.as_ref(),
+ self.off.as_ref(),
+ item.borrow(),
+ locale,
+ )?;
+ }
+ 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 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<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+{
+ DelayedFormat {
+ date: date.copied(),
+ time: time.copied(),
+ off: off.cloned(),
+ items,
+ #[cfg(feature = "unstable-locales")]
+ locale: None,
+ }
+ .fmt(w)
+}
+
+/// Formats single formatting item
+#[cfg(feature = "alloc")]
+#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt 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(),
+ #[cfg(feature = "unstable-locales")]
+ locale: None,
+ }
+ .fmt(w)
+}
+
+#[cfg(feature = "alloc")]
+fn format_inner(
+ w: &mut impl Write,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ item: &Item<'_>,
+ locale: Option<Locale>,
+) -> fmt::Result {
+ let locale = locale.unwrap_or(default_locale());
+
+ match *item {
+ 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, ref pad) => {
+ use self::Numeric::*;
+
+ let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun);
+ let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon);
+
+ let (width, v) = match *spec {
+ Year => (4, date.map(|d| i64::from(d.year()))),
+ YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))),
+ YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))),
+ IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
+ IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))),
+ IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(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).timestamp() - i64::from(off.local_minus_utc()))
+ }
+ (_, _, _) => None,
+ },
+ ),
+
+ // for the future expansion
+ Internal(ref int) => match int._dummy {},
+ };
+
+ if let Some(v) = v {
+ if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) {
+ // non-four-digit years require an explicit sign as per ISO 8601
+ match *pad {
+ Pad::None => write!(w, "{:+}", v),
+ Pad::Zero => write!(w, "{:+01$}", v, width + 1),
+ Pad::Space => write!(w, "{:+1$}", v, width + 1),
+ }
+ } else {
+ match *pad {
+ Pad::None => write!(w, "{}", v),
+ Pad::Zero => write!(w, "{:01$}", v, width),
+ Pad::Space => write!(w, "{:1$}", v, width),
+ }
+ }
+ } else {
+ Err(fmt::Error) // insufficient arguments for given format
+ }
+ }
+
+ Item::Fixed(ref spec) => {
+ use self::Fixed::*;
+
+ let ret = match *spec {
+ ShortMonthName => date.map(|d| {
+ w.write_str(short_months(locale)[d.month0() as usize])?;
+ Ok(())
+ }),
+ LongMonthName => date.map(|d| {
+ w.write_str(long_months(locale)[d.month0() as usize])?;
+ Ok(())
+ }),
+ ShortWeekdayName => date.map(|d| {
+ w.write_str(
+ short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize],
+ )?;
+ Ok(())
+ }),
+ LongWeekdayName => date.map(|d| {
+ w.write_str(
+ long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize],
+ )?;
+ Ok(())
+ }),
+ LowerAmPm => time.map(|t| {
+ let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] };
+ for c in ampm.chars().flat_map(|c| c.to_lowercase()) {
+ w.write_char(c)?
+ }
+ Ok(())
+ }),
+ UpperAmPm => time.map(|t| {
+ w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?;
+ Ok(())
+ }),
+ Nanosecond => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ if nano == 0 {
+ Ok(())
+ } else {
+ w.write_str(decimal_point(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, "{:09}", nano)
+ }
+ }
+ }),
+ Nanosecond3 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ w.write_str(decimal_point(locale))?;
+ write!(w, "{:03}", nano / 1_000_000)
+ }),
+ Nanosecond6 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ w.write_str(decimal_point(locale))?;
+ write!(w, "{:06}", nano / 1_000)
+ }),
+ Nanosecond9 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ w.write_str(decimal_point(locale))?;
+ write!(w, "{:09}", nano)
+ }),
+ Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
+ time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(w, "{:03}", nano / 1_000_000)
+ })
+ }
+ Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
+ time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(w, "{:06}", nano / 1_000)
+ })
+ }
+ Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
+ time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(w, "{:09}", nano)
+ })
+ }
+ TimezoneName => off.map(|(name, _)| {
+ w.write_str(name)?;
+ Ok(())
+ }),
+ TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| {
+ OffsetFormat {
+ precision: OffsetPrecision::Minutes,
+ colons: Colons::Maybe,
+ allow_zulu: *spec == TimezoneOffsetZ,
+ padding: Pad::Zero,
+ }
+ .format(w, off)
+ }),
+ TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| {
+ OffsetFormat {
+ precision: OffsetPrecision::Minutes,
+ colons: Colons::Colon,
+ allow_zulu: *spec == TimezoneOffsetColonZ,
+ padding: Pad::Zero,
+ }
+ .format(w, off)
+ }),
+ TimezoneOffsetDoubleColon => off.map(|&(_, off)| {
+ OffsetFormat {
+ precision: OffsetPrecision::Seconds,
+ colons: Colons::Colon,
+ allow_zulu: false,
+ padding: Pad::Zero,
+ }
+ .format(w, off)
+ }),
+ TimezoneOffsetTripleColon => off.map(|&(_, off)| {
+ OffsetFormat {
+ precision: OffsetPrecision::Hours,
+ colons: Colons::None,
+ allow_zulu: false,
+ padding: Pad::Zero,
+ }
+ .format(w, off)
+ }),
+ Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
+ return Err(fmt::Error);
+ }
+ RFC2822 =>
+ // same as `%a, %d %b %Y %H:%M:%S %z`
+ {
+ if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
+ Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off))
+ } else {
+ None
+ }
+ }
+ RFC3339 =>
+ // same as `%Y-%m-%dT%H:%M:%S%.f%:z`
+ {
+ if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
+ Some(write_rfc3339(
+ w,
+ crate::NaiveDateTime::new(*d, *t),
+ off.fix(),
+ SecondsFormat::AutoSi,
+ false,
+ ))
+ } else {
+ None
+ }
+ }
+ };
+
+ ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format
+ }
+
+ Item::Error => Err(fmt::Error),
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))]
+impl OffsetFormat {
+ /// Writes an offset from UTC with the format defined by `self`.
+ fn format(&self, w: &mut impl Write, 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.
+ let minutes = (off + 30) / 60;
+ 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)]
+#[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", feature = "rustc-serialize"))]
+pub(crate) fn write_rfc3339(
+ w: &mut impl Write,
+ 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, "{:+05}", year)?;
+ }
+ 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, ".{:09}", nano)?,
+ 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, ".{:09}", nano)?
+ }
+ }
+ 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,
+ 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, 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};
+
+ #[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("%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!("{:>4}", percent));
+ assert_eq!("% ", format!("{:<4}", percent));
+ assert_eq!(" % ", format!("{:^4}", percent));
+
+ // Item::Numeric, custom non-ASCII padding character
+ 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");
+ assert_eq!(" 2007 January 02", format!("{:>17}", ymd));
+ assert_eq!("2007 January 02 ", format!("{:<17}", ymd));
+ assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd));
+
+ // Truncated
+ let time = datetime.format("%T%.6f");
+ assert_eq!("12:34:56.1234", format!("{:.13}", time));
+ }
+
+ #[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"],
+ ],
+ );
+ }
+}
diff --git a/src/format/locales.rs b/src/format/locales.rs
index f7b4bbd..4cf4d41 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_match, Locale};
-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 a641f19..a822882 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -12,50 +12,69 @@
//! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of
//! the [`Item`](./enum.Item.html) type.
//! They are generated from more readable **format strings**;
-//! currently Chrono supports [one built-in syntax closely resembling
-//! C's `strftime` format](./strftime/index.html).
-
-#![allow(ellipsis_inclusive_range_patterns)]
+//! currently Chrono supports a built-in syntax closely resembling
+//! C's `strftime` format. The available options can be found [here](./strftime/index.html).
+//!
+//! # Example
+#![cfg_attr(not(feature = "std"), doc = "```ignore")]
+#![cfg_attr(feature = "std", doc = "```rust")]
+//! use chrono::{NaiveDateTime, TimeZone, Utc};
+//!
+//! 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 = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc();
+//! assert_eq!(parsed, date_time);
+//! # Ok::<(), chrono::ParseError>(())
+//! ```
-#[cfg(feature = "alloc")]
+#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::boxed::Box;
-#[cfg(feature = "alloc")]
-use alloc::string::{String, ToString};
-#[cfg(any(feature = "alloc", feature = "std", test))]
-use core::borrow::Borrow;
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(crate) use formatting::write_hundreds;
+#[cfg(feature = "alloc")]
+pub(crate) use formatting::write_rfc2822;
+#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))]
+pub(crate) use formatting::write_rfc3339;
+pub use formatting::SecondsFormat;
+#[cfg(feature = "alloc")]
+#[allow(deprecated)]
+pub use formatting::{format, format_item, DelayedFormat};
#[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)]
pub enum Pad {
/// No padding.
None,
@@ -78,10 +97,10 @@ 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)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
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,
@@ -134,24 +153,11 @@ 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, "<InternalNumeric>")
@@ -162,7 +168,7 @@ impl fmt::Debug for InternalNumeric {
///
/// 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)]
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Fixed {
/// Abbreviated month names.
///
@@ -208,6 +214,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,
@@ -234,12 +252,12 @@ 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)]
pub struct InternalFixed {
val: InternalInternal,
}
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum InternalInternal {
/// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but
/// allows missing minutes (per [ISO 8601][iso8601]).
@@ -258,18 +276,63 @@ enum InternalInternal {
Nanosecond9NoDot,
}
+/// Type for specifying the format of UTC offsets.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+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)]
+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)]
+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<str>),
/// 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<str>),
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
/// the parser simply ignores any padded whitespace and zeroes.
@@ -280,49 +343,57 @@ pub enum Item<'a> {
Error,
}
-macro_rules! lit {
- ($x:expr) => {
- Item::Literal($x)
- };
+const fn num(numeric: Numeric) -> Item<'static> {
+ Item::Numeric(numeric, Pad::None)
}
-macro_rules! sp {
- ($x:expr) => {
- Item::Space($x)
- };
-}
-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<'a> Item<'a> {
+ /// 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)]
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)]
+pub enum ParseErrorKind {
/// Given field is out of permitted range.
OutOfRange,
@@ -350,6 +421,10 @@ enum ParseErrorKind {
/// There was an error on the formatting string, or there were non-supported formating items.
BadFormat,
+
+ // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release.
+ #[doc(hidden)]
+ __Nonexhaustive,
}
/// Same as `Result<T, ParseError>`.
@@ -365,11 +440,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(feature = "std")]
impl Error for ParseError {
#[allow(deprecated)]
fn description(&self) -> &str {
@@ -378,465 +454,40 @@ 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<Locale>,
-) -> 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_str("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<Item = B> + Clone,
- B: Borrow<Item<'a>>,
-{
- 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<I> {
- /// The date view, if any.
- date: Option<NaiveDate>,
- /// The time view, if any.
- time: Option<NaiveTime>,
- /// 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.
- locale: Option<Locale>,
-}
-
-#[cfg(any(feature = "alloc", feature = "std", test))]
-impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
- /// Makes a new `DelayedFormat` value out of local date and time.
- pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
- 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<Off>(
- date: Option<NaiveDate>,
- time: Option<NaiveTime>,
- offset: &Off,
- items: I,
- ) -> DelayedFormat<I>
- 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<NaiveDate>,
- time: Option<NaiveTime>,
- items: I,
- locale: Locale,
- ) -> DelayedFormat<I> {
- 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<Off>(
- date: Option<NaiveDate>,
- time: Option<NaiveTime>,
- offset: &Off,
- items: I,
- locale: Locale,
- ) -> DelayedFormat<I>
- 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<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
- 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
///
-/// ~~~~
+/// ```
/// use chrono::Weekday;
///
/// assert_eq!("Sunday".parse::<Weekday>(), Ok(Weekday::Sun));
/// assert!("any day".parse::<Weekday>().is_err());
-/// ~~~~
+/// ```
///
/// The parsing is case-insensitive.
///
-/// ~~~~
+/// ```
/// # use chrono::Weekday;
/// assert_eq!("mON".parse::<Weekday>(), Ok(Weekday::Mon));
-/// ~~~~
+/// ```
///
/// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted.
///
-/// ~~~~
+/// ```
/// # use chrono::Weekday;
/// assert!("thurs".parse::<Weekday>().is_err());
-/// ~~~~
+/// ```
impl FromStr for Weekday {
type Err = ParseWeekdayError;
@@ -849,68 +500,31 @@ 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<Item = B> + Clone,
- B: Borrow<Item<'a>>,
-{
- 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
///
-/// ~~~~
+/// ```
/// use chrono::Month;
///
/// assert_eq!("January".parse::<Month>(), Ok(Month::January));
/// assert!("any day".parse::<Month>().is_err());
-/// ~~~~
+/// ```
///
/// The parsing is case-insensitive.
///
-/// ~~~~
+/// ```
/// # use chrono::Month;
/// assert_eq!("fEbruARy".parse::<Month>(), Ok(Month::February));
-/// ~~~~
+/// ```
///
/// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted.
///
-/// ~~~~
+/// ```
/// # use chrono::Month;
/// assert!("septem".parse::<Month>().is_err());
/// assert!("Augustin".parse::<Month>().is_err());
-/// ~~~~
+/// ```
impl FromStr for Month {
type Err = ParseMonthError;
diff --git a/src/format/parse.rs b/src/format/parse.rs
index 2fce827..26340d3 100644
--- a/src/format/parse.rs
+++ b/src/format/parse.rs
@@ -4,17 +4,15 @@
//! Date and time parsing routines.
-#![allow(deprecated)]
-
use core::borrow::Borrow;
use core::str;
use core::usize;
use super::scan;
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 super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT};
+use crate::{DateTime, FixedOffset, Weekday};
fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> {
p.set_weekday(match v {
@@ -53,7 +51,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 = <any char except '(', ')' and '\\'>
+ // c-escape = "\" <any char>
+ // 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 +80,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 +98,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 +108,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 +119,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 +134,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<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> {
macro_rules! try_consume {
($e:expr) => {{
let (s_, v) = $e?;
@@ -183,6 +187,8 @@ 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'-')?;
@@ -191,7 +197,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
parsed.set_day(try_consume!(scan::number(s, 2, 2)))?;
s = match s.as_bytes().first() {
- Some(&b't') | Some(&b'T') => &s[1..],
+ Some(&b't' | &b'T' | &b' ') => &s[1..],
Some(_) => return Err(INVALID),
None => return Err(TOO_SHORT),
};
@@ -206,8 +212,13 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
parsed.set_nanosecond(nanosecond)?;
}
- let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':')));
- if offset <= -86_400 || offset >= 86_400 {
+ let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true));
+ // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant.
+ // But it is possible to read the offset directly from `Parsed`. We want to only successfully
+ // populate `Parsed` if the input is fully valid RFC 3339.
+ // Max for the hours field is `23`, and for the minutes field `59`.
+ const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60;
+ if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) {
return Err(OUT_OF_RANGE);
}
parsed.set_offset(i64::from(offset))?;
@@ -236,7 +247,37 @@ where
I: Iterator<Item = B>,
B: Borrow<Item<'a>>,
{
- 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<Item = B>,
+ B: Borrow<Item<'a>>,
+{
+ parse_internal(parsed, s, items).map_err(|(_s, e)| e)
}
fn parse_internal<'a, 'b, I, B>(
@@ -272,7 +313,7 @@ where
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));
@@ -284,12 +325,12 @@ where
}
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) => {
@@ -322,7 +363,7 @@ 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));
@@ -408,36 +449,54 @@ where
}
&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))?;
}
&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))?;
}
&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))?;
}
&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))
+ }
}
}
@@ -446,489 +505,1339 @@ where
}
}
}
-
- // 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
+/// 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::<DateTime<FixedOffset>>()?;
+/// "2012-12-12 12:12:12Z".parse::<DateTime<FixedOffset>>()?;
+/// "2012- 12-12T12: 12:12Z".parse::<DateTime<FixedOffset>>()?;
+/// # Ok::<(), chrono::ParseError>(())
+/// ```
impl str::FromStr for DateTime<FixedOffset> {
type Err = ParseError;
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
- 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<Utc>`.
+/// - 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<Parsed> {
- let mut parsed = Parsed::new();
- parse(&mut parsed, s, items.iter())?;
- Ok(parsed)
- }
+ s = parse_internal(parsed, s, DATE_ITEMS.iter()).map_err(|(_s, e)| e)?;
- 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()).map_err(|(_s, e)| e)?;
+ 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!("\05", [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<DateTime<FixedOffset>> {
- 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 2 3 4 5",
+ &[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)],
+ parsed!(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<FixedOffset>) -> 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;
+ 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: 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<DateTime<FixedOffset>> {
+ #[track_caller]
+ fn check(s: &str, items: &[Item], expected: ParseResult<Parsed>) {
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);
}
- fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String {
- dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string()
+ #[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<DateTime<FixedOffset>> {
+ 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: {:?}\n Expect: {:?}", date, checkdate);
+ let dt = rfc2822_to_datetime(date); // parse a date
+ if dt != checkdate {
+ // check for expected result
+ panic!(
+ "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
+ date, dt, checkdate
+ );
+ }
+ }
}
- // 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 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]
+ 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)
+ ("2015-01-20 17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T'
+ ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD
+ ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS
+ ("-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-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::<FixedOffset>::parse_from_rfc3339(date);
+ if dt != checkdate {
+ // check for expected result
+ panic!(
+ "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
+ date, dt, 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 b8ed2d9..19f7f81 100644
--- a/src/format/parsed.rs
+++ b/src/format/parsed.rs
@@ -4,16 +4,10 @@
//! 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};
+use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
+use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone};
+use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday};
/// Parsed parts of date and time. There are two classes of methods:
///
@@ -22,8 +16,8 @@ use {Datelike, Timelike};
///
/// - `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)]
+#[allow(clippy::manual_non_exhaustive)]
+#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)]
pub struct Parsed {
/// Year.
///
@@ -107,6 +101,7 @@ pub struct Parsed {
pub offset: Option<i32>,
/// A dummy field to make this type not fully destructible (required for API stability).
+ // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release.
_dummy: (),
}
@@ -126,36 +121,9 @@ fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<(
}
}
-impl Default for Parsed {
- fn default() -> Parsed {
- Parsed {
- year: None,
- year_div_100: None,
- year_mod_100: None,
- isoyear: None,
- isoyear_div_100: None,
- isoyear_mod_100: None,
- month: None,
- week_from_sun: None,
- week_from_mon: None,
- isoweek: None,
- weekday: None,
- ordinal: None,
- day: None,
- hour_div_12: None,
- hour_mod_12: None,
- minute: None,
- second: None,
- nanosecond: None,
- timestamp: None,
- offset: None,
- _dummy: (),
- }
- }
-}
-
impl Parsed {
/// Returns the initial value of parsed parts.
+ #[must_use]
pub fn new() -> Parsed {
Parsed::default()
}
@@ -163,7 +131,7 @@ impl Parsed {
/// Tries to set the [`year`](#structfield.year) field from given 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.
@@ -172,7 +140,7 @@ impl Parsed {
if value < 0 {
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, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value.
@@ -181,13 +149,13 @@ impl Parsed {
if value < 0 {
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, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`isoyear`](#structfield.isoyear) field from given 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.
@@ -196,7 +164,10 @@ impl Parsed {
if value < 0 {
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,
+ i32::try_from(value).map_err(|_| OUT_OF_RANGE)?,
+ )
}
/// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value.
@@ -205,31 +176,34 @@ impl Parsed {
if value < 0 {
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,
+ i32::try_from(value).map_err(|_| OUT_OF_RANGE)?,
+ )
}
/// Tries to set the [`month`](#structfield.month) field from given 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)?)
+ set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given 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)?)
+ set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given 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)?)
+ set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`isoweek`](#structfield.isoweek) field from given 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)?)
+ set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`weekday`](#structfield.weekday) field from given value.
@@ -241,27 +215,27 @@ impl Parsed {
/// Tries to set the [`ordinal`](#structfield.ordinal) field from given 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)?)
+ set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`day`](#structfield.day) field from given 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)?)
+ set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value.
/// (`false` for AM, `true` for PM)
#[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, u32::from(value))
}
/// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from
/// given hour number in 12-hour clocks.
#[inline]
pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> {
- if value < 1 || value > 12 {
+ if !(1..=12).contains(&value) {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.hour_mod_12, value as u32 % 12)
@@ -271,7 +245,7 @@ impl Parsed {
/// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value.
#[inline]
pub fn set_hour(&mut self, value: i64) -> ParseResult<()> {
- let v = value.to_u32().ok_or(OUT_OF_RANGE)?;
+ let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?;
set_if_consistent(&mut self.hour_div_12, v / 12)?;
set_if_consistent(&mut self.hour_mod_12, v % 12)?;
Ok(())
@@ -280,19 +254,19 @@ impl Parsed {
/// Tries to set the [`minute`](#structfield.minute) field from given 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)?)
+ set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`second`](#structfield.second) field from given 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)?)
+ set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given 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)?)
+ set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}
/// Tries to set the [`timestamp`](#structfield.timestamp) field from given value.
@@ -304,7 +278,7 @@ impl Parsed {
/// Tries to set the [`offset`](#structfield.offset) field from given 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.
@@ -333,11 +307,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);
}
- 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 {
@@ -347,7 +322,7 @@ 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);
}
@@ -357,7 +332,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),
@@ -372,8 +347,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
};
@@ -393,8 +367,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
};
@@ -408,9 +381,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
@@ -457,7 +429,7 @@ impl Parsed {
+ (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)))
+ .checked_add_signed(TimeDelta::days(i64::from(ndays)))
.ok_or(OUT_OF_RANGE)?;
if date.year() != year {
return Err(OUT_OF_RANGE);
@@ -491,7 +463,7 @@ impl Parsed {
+ (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)))
+ .checked_add_signed(TimeDelta::days(i64::from(ndays)))
.ok_or(OUT_OF_RANGE)?;
if date.year() != year {
return Err(OUT_OF_RANGE);
@@ -528,32 +500,32 @@ impl Parsed {
/// It is able to handle leap seconds when given second is 60.
pub fn to_naive_time(&self) -> ParseResult<NaiveTime> {
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,
};
@@ -614,7 +586,7 @@ impl Parsed {
59 => {}
// `datetime` is known to be off by one second.
0 => {
- datetime -= OldDuration::seconds(1);
+ datetime -= TimeDelta::seconds(1);
}
// otherwise it is impossible.
_ => return Err(IMPOSSIBLE),
@@ -652,9 +624,15 @@ impl Parsed {
/// plus a time zone offset.
/// Either way those fields have to be consistent to each other.
pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> {
- 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),
@@ -721,10 +699,10 @@ impl Parsed {
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::naive::{NaiveDate, NaiveTime};
+ use crate::offset::{FixedOffset, TimeZone, Utc};
+ use crate::Datelike;
+ use crate::Weekday::*;
#[test]
fn test_parsed_set_fields() {
@@ -805,7 +783,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));
@@ -851,7 +829,7 @@ 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();
+ 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),
@@ -992,8 +970,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));
@@ -1048,9 +1026,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));
@@ -1104,14 +1085,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()),
@@ -1119,7 +1101,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
@@ -1198,7 +1180,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));
@@ -1242,7 +1232,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;
@@ -1251,33 +1248,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 0efb1ee..1ab87b9 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 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,12 +42,12 @@ 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, i64)> {
// record the number of digits consumed for later scaling.
let origlen = s.len();
let (s, v) = number(s, 1, 9)?;
@@ -79,14 +59,14 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
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 +79,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 +103,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 +123,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 +143,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 +160,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 +169,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,30 +181,41 @@ 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<F>(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<F>(
+/// 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<F>(
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)> {
+ if allow_zulu {
+ if let Some(&b'Z' | &b'z') = s.as_bytes().first() {
+ return Ok((&s[1..], 0));
+ }
+ }
+
+ const fn digits(s: &str) -> ParseResult<(u8, u8)> {
let b = s.as_bytes();
if b.len() < 2 {
Err(TOO_SHORT)
@@ -230,17 +223,35 @@ where
Ok((b[0], b[1]))
}
}
- let negative = match s.as_bytes().first() {
- Some(&b'+') => false,
- Some(&b'-') => true,
+ 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 +263,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 +274,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 +282,155 @@ 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<F>(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<F>(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<i32>)> {
- // 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_else(|| 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::format::{INVALID, TOO_SHORT};
+ use crate::Weekday;
+
+ #[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,
+ "{:?} expected to produce {:?}, but produced {:?}.",
+ test_in, expected, 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 93820a2..26fa391 100644
--- a/src/format/strftime.rs
+++ b/src/format/strftime.rs
@@ -11,9 +11,9 @@ 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] |
| | | |
| `%m` | `07` | Month number (01--12), zero-padded to 2 digits. |
| `%b` | `Jul` | Abbreviated month name. Always 3 letters. |
@@ -28,12 +28,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 +52,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 +97,55 @@ 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.
-[^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.
<br>
<br>
+ This format also supports having a `Z` or `UTC` in place of `%:z`. They
+ are equivalent to `+00:00`.
+ <br>
+ <br>
+ Note that all `T`, `Z`, and `UTC` are parsed case-insensitively.
+ <br>
+ <br>
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`:
- <br>
- 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.
- <br>
+[^7]: `%f`, `%.f`:
<br>
- 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 `.`.
- <br>
- <br>
- 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.<br>
+ `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a
+ second.<br>
+ 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.
<br>
<br>
- 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,136 +156,311 @@ 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;
+use super::{fixed, internal_fixed, num, num0, nums};
#[cfg(feature = "unstable-locales")]
-type Fmt<'a> = Vec<Item<'a>>;
-#[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::{locales, Locale};
+use super::{Fixed, InternalInternal, Item, Numeric, Pad};
+#[cfg(any(feature = "alloc", feature = "std"))]
+use super::{ParseError, BAD_FORMAT};
+#[cfg(feature = "alloc")]
+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)]
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>],
+ #[cfg(feature = "unstable-locales")]
+ locale_str: &'a str,
+ #[cfg(feature = "unstable-locales")]
+ locale: Option<Locale>,
}
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 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
+ ///
+ #[cfg_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")]
+ #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")]
+ /// 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> {
+ #[cfg(not(feature = "unstable-locales"))]
+ {
+ StrftimeItems { remainder: s, queue: &[] }
+ }
+ #[cfg(feature = "unstable-locales")]
+ {
+ StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None }
+ }
}
- /// Creates a new parsing iterator from the `strftime`-like format string.
+ /// 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_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")]
+ #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")]
+ /// 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")]
- 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();
-
- StrftimeItems {
- remainder: s,
- recons: Vec::new(),
- d_fmt: d_fmt,
- d_t_fmt: d_t_fmt,
- t_fmt: t_fmt,
- }
+ #[must_use]
+ pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
+ StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) }
}
- #[cfg(not(feature = "unstable-locales"))]
- fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
- static FMT_NONE: &'static [Item<'static>; 0] = &[];
-
- StrftimeItems {
- remainder: s,
- recons: FMT_NONE,
- d_fmt: D_FMT,
- d_t_fmt: D_T_FMT,
- t_fmt: T_FMT,
- }
+ /// 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.
+ ///
+ /// # 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<Vec<Item<'a>>, ParseError> {
+ self.into_iter()
+ .map(|item| match item == Item::Error {
+ false => Ok(item),
+ true => Err(BAD_FORMAT),
+ })
+ .collect()
}
- #[cfg(feature = "unstable-locales")]
- fn with_remainer(s: &'a str) -> 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(),
- }
+ /// 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.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::format::{Item, ParseError, StrftimeItems};
+ /// use chrono::NaiveDate;
+ ///
+ /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result<Vec<Item<'static>>, 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<Vec<Item<'static>>, ParseError> {
+ self.into_iter()
+ .map(|item| match item == Item::Error {
+ false => Ok(item.to_owned()),
+ true => Err(BAD_FORMAT),
+ })
+ .collect()
}
}
-const HAVE_ALTERNATES: &'static str = "z";
+const HAVE_ALTERNATES: &str = "z";
impl<'a> Iterator for StrftimeItems<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
- // we have some reconstructed items to return
- if !self.recons.is_empty() {
- let item;
- #[cfg(feature = "unstable-locales")]
- {
- item = self.recons.remove(0);
- }
- #[cfg(not(feature = "unstable-locales"))]
- {
- item = self.recons[0].clone();
- self.recons = &self.recons[1..];
- }
+ // 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);
}
- match self.remainder.chars().next() {
+ // Normal: we are parsing the formatting string.
+ let (remainder, item) = self.parse_next_item(self.remainder)?;
+ self.remainder = remainder;
+ Some(item)
+ }
+}
+
+impl<'a> StrftimeItems<'a> {
+ fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
+ use InternalInternal::*;
+ use Item::{Literal, Space};
+ use Numeric::*;
+
+ static D_FMT: &[Item<'static>] =
+ &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
+ static D_T_FMT: &[Item<'static>] = &[
+ fixed(Fixed::ShortWeekdayName),
+ Space(" "),
+ fixed(Fixed::ShortMonthName),
+ Space(" "),
+ nums(Day),
+ Space(" "),
+ num0(Hour),
+ Literal(":"),
+ num0(Minute),
+ Literal(":"),
+ num0(Second),
+ Space(" "),
+ num0(Year),
+ ];
+ static T_FMT: &[Item<'static>] =
+ &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
+ static T_FMT_AMPM: &[Item<'static>] = &[
+ num0(Hour12),
+ Literal(":"),
+ num0(Minute),
+ Literal(":"),
+ num0(Second),
+ Space(" "),
+ fixed(Fixed::UpperAmPm),
+ ];
+
+ match remainder.chars().next() {
// we are done
None => None,
// the next item is a specifier
Some('%') => {
- self.remainder = &self.remainder[1..];
+ remainder = &remainder[1..];
macro_rules! next {
() => {
- match self.remainder.chars().next() {
+ match remainder.chars().next() {
Some(x) => {
- self.remainder = &self.remainder[x.len_utf8()..];
+ remainder = &remainder[x.len_utf8()..];
x
}
- None => return Some(Item::Error), // premature end of string
+ None => return Some((remainder, Item::Error)), // premature end of string
}
};
}
@@ -312,338 +475,661 @@ impl<'a> Iterator for StrftimeItems<'a> {
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);
+ return Some((remainder, Item::Error));
}
- macro_rules! recons {
+ macro_rules! queue {
[$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;
- }
+ const QUEUE: &'static [Item<'static>] = &[$($tail),+];
+ self.queue = QUEUE;
$head
})
}
-
- macro_rules! recons_from_slice {
+ #[cfg(not(feature = "unstable-locales"))]
+ macro_rules! queue_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..];
- }
+ self.queue = &$slice[1..];
$slice[0].clone()
}};
}
let item = match spec {
- 'A' => fix!(LongWeekdayName),
- 'B' => fix!(LongMonthName),
- 'C' => num0!(YearDiv100),
+ 'A' => fixed(Fixed::LongWeekdayName),
+ 'B' => fixed(Fixed::LongMonthName),
+ 'C' => num0(YearDiv100),
'D' => {
- recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]
+ 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)]
}
- '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),
+ '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),
+ #[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)
+ }
+ }
+ 's' => num(Timestamp),
+ 't' => Space("\t"),
+ 'u' => num(WeekdayFromMon),
'v' => {
- recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)]
+ queue![
+ nums(Day),
+ Literal("-"),
+ fixed(Fixed::ShortMonthName),
+ Literal("-"),
+ num0(Year)
+ ]
}
- 'w' => num!(NumDaysFromSun),
- 'x' => recons_from_slice!(self.d_fmt),
- 'y' => num0!(YearMod100),
+ '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_fix!(TimezoneOffsetPermissive)
+ internal_fixed(TimezoneOffsetPermissive)
} else {
- fix!(TimezoneOffset)
+ fixed(Fixed::TimezoneOffset)
+ }
+ }
+ '+' => 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 {
+ Item::Error
}
}
- '+' => fix!(RFC3339),
- ':' => match next!() {
- 'z' => fix!(TimezoneOffsetColon),
- _ => Item::Error,
- },
'.' => match next!() {
'3' => match next!() {
- 'f' => fix!(Nanosecond3),
+ 'f' => fixed(Fixed::Nanosecond3),
_ => Item::Error,
},
'6' => match next!() {
- 'f' => fix!(Nanosecond6),
+ 'f' => fixed(Fixed::Nanosecond6),
_ => Item::Error,
},
'9' => match next!() {
- 'f' => fix!(Nanosecond9),
+ 'f' => fixed(Fixed::Nanosecond9),
_ => Item::Error,
},
- 'f' => fix!(Nanosecond),
+ 'f' => fixed(Fixed::Nanosecond),
_ => Item::Error,
},
'3' => match next!() {
- 'f' => internal_fix!(Nanosecond3NoDot),
+ 'f' => internal_fixed(Nanosecond3NoDot),
_ => Item::Error,
},
'6' => match next!() {
- 'f' => internal_fix!(Nanosecond6NoDot),
+ 'f' => internal_fixed(Nanosecond6NoDot),
_ => Item::Error,
},
'9' => match next!() {
- 'f' => internal_fix!(Nanosecond9NoDot),
+ 'f' => internal_fixed(Nanosecond9NoDot),
_ => Item::Error,
},
- '%' => lit!("%"),
+ '%' => Literal("%"),
_ => Item::Error, // no such specifier
};
- // adjust `item` if we have any padding modifier
+ // 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.recons.is_empty() => {
- Some(Item::Numeric(kind.clone(), new_pad))
+ Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
+ Some((remainder, Item::Numeric(kind.clone(), new_pad)))
}
- _ => Some(Item::Error), // no reconstructed or non-numeric item allowed
+ _ => Some((remainder, Item::Error)),
}
} else {
- Some(item)
+ Some((remainder, item))
}
}
// 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_else(|| self.remainder.len());
+ let nextspec =
+ remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
assert!(nextspec > 0);
- let item = sp!(&self.remainder[..nextspec]);
- self.remainder = &self.remainder[nextspec..];
- Some(item)
+ let item = Space(&remainder[..nextspec]);
+ remainder = &remainder[nextspec..];
+ Some((remainder, item))
}
// the next item is literal
_ => {
- let nextspec = self
- .remainder
+ let nextspec = remainder
.find(|c: char| c.is_whitespace() || c == '%')
- .unwrap_or_else(|| self.remainder.len());
+ .unwrap_or(remainder.len());
assert!(nextspec > 0);
- let item = lit!(&self.remainder[..nextspec]);
- self.remainder = &self.remainder[nextspec..];
- Some(item)
+ let item = Literal(&remainder[..nextspec]);
+ remainder = &remainder[nextspec..];
+ Some((remainder, item))
}
}
}
+
+ #[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_items() {
- fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
- // 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::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
+mod tests {
+ use super::StrftimeItems;
+ use crate::format::Item::{self, Literal, Space};
+ #[cfg(feature = "unstable-locales")]
+ use crate::format::Locale;
+ use crate::format::{fixed, internal_fixed, num, num0, nums};
+ use crate::format::{Fixed, InternalInternal, Numeric::*};
+ #[cfg(feature = "alloc")]
+ use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
+
+ #[test]
+ fn test_strftime_items() {
+ fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
+ // 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::<Option<Vec<_>>>().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]);
}
- 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]);
-}
+ #[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();
-#[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(), "%");
-}
+ // 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(), "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");
-#[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"
- );
+ // 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 <https://github.com/chronotope/chrono/issues/1139>.
+ #[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::<Item>(), 24);
+ assert_eq!(size_of::<StrftimeItems>(), 56);
+ assert_eq!(size_of::<Locale>(), 2);
+ }
+
+ #[test]
+ #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
+ fn test_type_sizes() {
+ use core::mem::size_of;
+ assert_eq!(size_of::<Item>(), 12);
+ assert_eq!(size_of::<StrftimeItems>(), 28);
+ assert_eq!(size_of::<Locale>(), 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");
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index e2608d3..18d1faa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,35 +1,21 @@
-// 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](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar):
//!
-//! ## Usage
+//! * 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
+//! [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html).
+//! * Configurable parsing and formatting with a `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.
//!
-//! Put this in your `Cargo.toml`:
-//!
-//! ```toml
-//! [dependencies]
-//! chrono = "0.4"
-//! ```
+//! 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.
//!
//! ### Features
//!
@@ -42,16 +28,20 @@
//! - `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.
+//! - `clock`: Enables reading the system time (`now`) that depends on the standard library for
+//! UNIX-like operating systems and the Windows API (`winapi`) for Windows.
+//! - `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.
+//! - `rkyv`: Enable serialization/deserialization via rkyv.
+//! - `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 langer has a function, but once offered compatibility with the
+//! `time` 0.1 crate.
//!
//! [`serde`]: https://github.com/serde-rs/serde
//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen
@@ -62,31 +52,18 @@
//!
//! ## 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)
+//! ### Time delta / Duration
+//!
+//! 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.
+//!
+//! 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.
+//!
+//! 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
@@ -126,44 +103,62 @@
//! or in the local time zone
//! ([`Local::now()`](./offset/struct.Local.html#method.now)).
//!
-//! ```rust
+#![cfg_attr(not(feature = "now"), doc = "```ignore")]
+#![cfg_attr(feature = "now", doc = "```rust")]
//! use chrono::prelude::*;
//!
//! let utc: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
+//! # let _ = utc;
+//! ```
+//!
+#![cfg_attr(not(feature = "clock"), doc = "```ignore")]
+#![cfg_attr(feature = "clock", doc = "```rust")]
+//! use chrono::prelude::*;
+//!
//! let local: DateTime<Local> = 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.
//!
-//! ```rust
+#![cfg_attr(not(feature = "now"), doc = "```ignore")]
+#![cfg_attr(feature = "now", doc = "```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`
+//! # 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_local_timezone(Utc).unwrap());
+//!
//! // 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));
+//! assert_eq!(dt, NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc());
//!
-//! 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));
+//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_milli_opt(9, 10, 11, 12)?.and_local_timezone(Utc).unwrap(); // `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_local_timezone(Utc).unwrap());
+//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_nano_opt(9, 10, 11, 12_000_000)?.and_local_timezone(Utc).unwrap());
//!
//! // 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),
+//! LocalResult::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), LocalResult::None);
+//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::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.
@@ -173,14 +168,11 @@
//! 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 +185,8 @@
//!
//! // 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_local_timezone(Utc).unwrap());
//!
//! // a sample of property manipulations (validates dynamically)
//! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday
@@ -202,15 +194,14 @@
//! 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::seconds(-2 * 3600 + 2));
+//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2));
+//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000),
+//! 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::seconds(1_000_000_000),
+//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap());
//! ```
//!
//! ### Formatting and Parsing
@@ -230,32 +221,41 @@
//! help of an additional C library. This functionality is under the feature
//! `unstable-locales`:
//!
-//! ```text
-//! chrono { version = "0.4", features = ["unstable-locales"]
+//! ```toml
+//! chrono = { version = "0.4", features = ["unstable-locales"] }
//! ```
//!
//! The `unstable-locales` feature requires and implies at least the `alloc` feature.
//!
//! ```rust
+//! # #[allow(unused_imports)]
//! use chrono::prelude::*;
//!
-//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9);
+//! # #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
+//! # fn test() {
+//! 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("%a %b %e %T %Y").to_string(), dt.format("%c").to_string());
//!
+//! 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);
+//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap().and_local_timezone(Utc).unwrap();
//! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z");
+//! # }
+//! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))]
+//! # fn test() {}
+//! # if cfg!(all(feature = "unstable-locales", feature = "alloc")) {
+//! # test();
+//! # }
//! ```
//!
-//! Parsing can be done with three methods:
+//! Parsing can be done with two 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
@@ -273,20 +273,14 @@
//! [`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.
//!
//! ```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::<DateTime<Utc>>(), Ok(dt.clone()));
@@ -300,16 +294,12 @@
//! 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)
@@ -317,21 +307,22 @@
//!
//! ### Conversion from and to EPOCH timestamps
//!
-//! Use [`Utc.timestamp(seconds, nanoseconds)`](./offset/trait.TimeZone.html#method.timestamp)
-//! to construct a [`DateTime<Utc>`](./struct.DateTime.html) from a UNIX timestamp
+//! Use [`DateTime::from_timestamp(seconds, nanoseconds)`](DateTime::from_timestamp)
+//! to construct a [`DateTime<Utc>`] 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_attr(not(feature = "std"), doc = "```ignore")]
+#![cfg_attr(feature = "std", doc = "```rust")]
//! // 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<Utc> = DateTime::from_timestamp(1_500_000_000, 0).unwrap();
//! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
//!
//! // Get epoch value from a datetime:
@@ -339,33 +330,6 @@
//! 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`
@@ -385,1150 +349,281 @@
//!
//! ## 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/")]
+//! * 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).
+//!
+//! ## 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))))]
#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
-// lints are added all the time, we test on 1.13
-#![allow(unknown_lints)]
+#![warn(unreachable_pub)]
+#![deny(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,
- // Similarly, redundant_field_names lints on not using the
- // field-init-shorthand, which was stabilized in rust 1.17.
- redundant_field_names,
- // 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,
-))]
+// can remove this if/when rustc-serialize support is removed
+// keeps clippy happy in the meantime
+#![cfg_attr(feature = "rustc-serialize", allow(deprecated))]
+#![cfg_attr(docsrs, feature(doc_auto_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;
+#[cfg(feature = "std")]
+#[doc(no_inline)]
+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};
+mod datetime;
#[cfg(feature = "rustc-serialize")]
pub use datetime::rustc_serialize::TsSeconds;
-pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_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;
+pub use offset::LocalResult;
+#[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 month;
+#[doc(no_inline)]
+pub use month::ParseMonthError;
+pub use month::{Month, Months};
- /// 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 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.
+///
+/// This module provides default implementations for `DateTime` using the [RFC 3339][1] format and various
+/// alternatives for use with serde's [`with` annotation][2].
///
-/// 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.
+/// *Available on crate feature 'serde' only.*
///
-/// [1]: https://serde.rs/attributes.html#field-attributes
-/// [2]: https://tools.ietf.org/html/rfc3339
+/// [1]: https://tools.ietf.org/html/rfc3339
+/// [2]: https://serde.rs/field-attrs.html#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
-
-#[cfg(feature = "serde")]
-enum SerdeError<V: fmt::Display, D: fmt::Display> {
- NonExistent { timestamp: V },
- Ambiguous { timestamp: V, min: D, max: D },
-}
-
-/// Construct a [`SerdeError::NonExistent`]
-#[cfg(feature = "serde")]
-fn ne_timestamp<T: fmt::Display>(ts: T) -> SerdeError<T, u8> {
- SerdeError::NonExistent::<T, u8> { timestamp: ts }
-}
-
-#[cfg(feature = "serde")]
-impl<V: fmt::Display, D: fmt::Display> fmt::Debug for SerdeError<V, D> {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "ChronoSerdeError({})", self)
- }
-}
-
-// impl<V: fmt::Display, D: fmt::Debug> core::error::Error for SerdeError<V, D> {}
-#[cfg(feature = "serde")]
-impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- &SerdeError::NonExistent { ref timestamp } => {
- write!(f, "value is not a legal timestamp: {}", timestamp)
- }
- &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<Weekday> {
- 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<Weekday> {
- 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- 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 {
- write!(f, "Weekday")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
- 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<D>(deserializer: D) -> Result<Self, D::Error>
- 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::<Weekday>(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::<Weekday>(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,
- }
- }
-
- /// 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",
- }
+ write!(f, "out of range")
}
}
-impl num_traits::FromPrimitive for Month {
- /// Returns an Option<Month> 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<Month> {
- Self::from_u32(n as u32)
- }
-
- #[inline]
- fn from_i64(n: i64) -> Option<Month> {
- Self::from_u32(n as u32)
- }
-
- #[inline]
- fn from_u32(n: u32) -> Option<Month> {
- 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 `<Month>` 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- 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 {
- write!(f, "Month")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
- 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<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- deserializer.deserialize_str(MonthVisitor)
- }
- }
+#[cfg(feature = "std")]
+impl std::error::Error for OutOfRange {}
- #[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::<Month>(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::<Month>(string).unwrap_err();
- }
- }
-}
-
-/// 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;
-
- /// 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.
- ///
- /// Returns `None` when the resulting value would be invalid.
- fn with_year(&self, year: i32) -> Option<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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;
- }
- 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;
+/// 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,
}
- (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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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<Self>;
-
- /// 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()
- }
+ };
}
-#[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));
+/// Workaround because `.expect()` is not (yet) available in const context.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! expect {
+ ($e:expr, $m:literal) => {
+ match $e {
+ Some(v) => v,
+ None => panic!($m),
}
- }
-
- #[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: Datelike>(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);
- }
-}
-
-#[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 0000000..4353dda
--- /dev/null
+++ b/src/month.rs
@@ -0,0 +1,443 @@
+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 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(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
+#[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 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",
+ }
+ }
+}
+
+impl TryFrom<u8> for Month {
+ type Error = OutOfRange;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ 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<Month>` 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<Month> {
+ Self::from_u32(n as u32)
+ }
+
+ #[inline]
+ fn from_i64(n: i64) -> Option<Month> {
+ Self::from_u32(n as u32)
+ }
+
+ #[inline]
+ fn from_u32(n: u32) -> Option<Month> {
+ 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))]
+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 `<Month>` value with `FromStr`.
+#[derive(Clone, PartialEq, Eq)]
+pub struct ParseMonthError {
+ pub(crate) _dummy: (),
+}
+
+#[cfg(feature = "std")]
+impl std::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 = "serde")]
+mod month_serde {
+ use super::Month;
+ use serde::{de, ser};
+
+ use core::fmt;
+
+ impl ser::Serialize for Month {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ 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<D>(deserializer: D) -> Result<Self, D::Error>
+ 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 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]
+ #[cfg(feature = "serde")]
+ fn test_serde_deserialize() {
+ use 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::<Month>(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::<Month>(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::<Month>(&bytes).unwrap(), month);
+ }
+}
diff --git a/src/naive/date.rs b/src/naive/date.rs
index 3e34e20..190a6ba 100644
--- a/src/naive/date.rs
+++ b/src/naive/date.rs
@@ -3,20 +3,29 @@
//! ISO 8601 calendar date without timezone.
-#[cfg(any(feature = "alloc", feature = "std", test))]
+#[cfg(feature = "alloc")]
use core::borrow::Borrow;
-use core::ops::{Add, AddAssign, Sub, SubAssign};
+use core::iter::FusedIterator;
+use core::ops::{Add, AddAssign, RangeInclusive, 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};
+#[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;
+
+#[cfg(feature = "alloc")]
+use crate::format::DelayedFormat;
+use crate::format::{
+ parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult,
+ Parsed, StrftimeItems,
+};
+use crate::month::Months;
+use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime};
+use crate::{expect, try_opt};
+use crate::{Datelike, TimeDelta, Weekday};
use super::internals::{self, DateImpl, Mdf, Of, YearFlags};
use super::isoweek;
@@ -24,32 +33,114 @@ 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
- - 146097_000;
-
-#[cfg(test)] // only used for testing, but duplicated in naive::datetime
-const MAX_BITS: usize = 44;
+/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
+/// day of the week.
+#[derive(Debug)]
+pub struct NaiveWeek {
+ date: NaiveDate,
+ start: Weekday,
+}
+
+impl NaiveWeek {
+ /// 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]
+ pub const fn first_day(&self) -> NaiveDate {
+ 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 };
+ expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`")
+ }
+
+ /// 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]
+ pub const fn last_day(&self) -> NaiveDate {
+ 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 };
+ expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`")
+ }
+
+ /// Returns a [`RangeInclusive<T>`] 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]
+ pub const fn days(&self) -> RangeInclusive<NaiveDate> {
+ self.first_day()..=self.last_day()
+ }
+}
+
+/// 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<TimeZone>` data types and in other cases
+/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+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)
+ }
+}
/// ISO 8601 calendar date without timezone.
-/// Allows for every [proleptic Gregorian date](#calendar-date)
-/// from Jan 1, 262145 BCE to Dec 31, 262143 CE.
+/// 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
@@ -67,12 +158,12 @@ const MAX_BITS: usize = 44;
///
/// * 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.
+/// 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](../enum.Weekday.html) with the following rules:
+/// 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.
@@ -83,10 +174,9 @@ const MAX_BITS: usize = 44;
/// * 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.
+/// 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
///
@@ -95,92 +185,97 @@ const MAX_BITS: usize = 44;
/// 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 {
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*/ };
+#[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).
-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()
- );
+#[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")]
+pub const MAX_DATE: NaiveDate = NaiveDate::MAX;
- // 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
- );
+#[cfg(all(feature = "arbitrary", feature = "std"))]
+impl arbitrary::Arbitrary<'_> for NaiveDate {
+ fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<NaiveDate> {
+ 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 {
- /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification.
- fn from_of(year: i32, of: Of) -> Option<NaiveDate> {
- if year >= MIN_YEAR && year <= MAX_YEAR && of.valid() {
- let Of(of) = of;
- Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) })
- } else {
- None
+ pub(crate) fn weeks_from(&self, day: Weekday) -> i32 {
+ (self.ordinal() as i32 - self.weekday().num_days_from(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<NaiveDate> {
+ if year < MIN_YEAR || year > MAX_YEAR {
+ return None; // Out-of-range
+ }
+ debug_assert!(YearFlags::from_year(year).0 == flags.0);
+ match Of::new(ordinal, flags) {
+ Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }),
+ None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags.
}
}
- /// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification.
- fn from_mdf(year: i32, mdf: Mdf) -> Option<NaiveDate> {
- NaiveDate::from_of(year, mdf.to_of())
+ /// 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<NaiveDate> {
+ if year < MIN_YEAR || year > MAX_YEAR {
+ return None; // Out-of-range
+ }
+ match mdf.to_of() {
+ Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }),
+ None => None, // Non-existing date
+ }
}
/// 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};
+ /// # Panics
///
- /// 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")
+ /// 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).
///
- /// Returns `None` on the out-of-range date, invalid month and/or 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;
@@ -191,44 +286,44 @@ impl NaiveDate {
/// 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<NaiveDate> {
+ /// ```
+ #[must_use]
+ pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
let flags = YearFlags::from_year(year);
- NaiveDate::from_mdf(year, Mdf::new(month, day, flags))
+
+ 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 on the out-of-range date and/or invalid day of year.
- ///
- /// # Example
- ///
- /// ~~~~
- /// use chrono::{NaiveDate, Datelike, Weekday};
+ /// # Panics
///
- /// 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")
+ /// 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).
///
- /// Returns `None` on the out-of-range date and/or invalid day of 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;
@@ -240,49 +335,44 @@ impl NaiveDate {
/// 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<NaiveDate> {
+ /// ```
+ #[must_use]
+ pub const fn from_yo_opt(year: i32, ordinal: u32) -> Option<NaiveDate> {
let flags = YearFlags::from_year(year);
- NaiveDate::from_of(year, Of::new(ordinal, flags))
+ 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 on the out-of-range date and/or invalid week number.
- ///
- /// # Example
- ///
- /// ~~~~
- /// use chrono::{NaiveDate, Datelike, Weekday};
+ /// # Panics
///
- /// 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")
+ /// 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.
///
- /// Returns `None` on the out-of-range date and/or invalid week number.
+ /// # 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 = NaiveDate::from_ymd;
+ /// 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);
@@ -292,13 +382,13 @@ impl NaiveDate {
///
/// 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_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,
@@ -314,8 +404,9 @@ impl NaiveDate {
/// 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<NaiveDate> {
+ /// ```
+ #[must_use]
+ pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option<NaiveDate> {
let flags = YearFlags::from_year(year);
let nweeks = flags.nisoweeks();
if 1 <= week && week <= nweeks {
@@ -325,20 +416,21 @@ impl NaiveDate {
if weekord <= delta {
// ordinal < 1, previous year
let prevflags = YearFlags::from_year(year - 1);
- NaiveDate::from_of(
+ NaiveDate::from_ordinal_and_flags(
year - 1,
- Of::new(weekord + prevflags.ndays() - delta, prevflags),
+ 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))
+ NaiveDate::from_ordinal_and_flags(year, ordinal, flags)
} else {
// ordinal > ndays, next year
let nextflags = YearFlags::from_year(year + 1);
- NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags))
+ NaiveDate::from_ordinal_and_flags(year + 1, ordinal - ndays, nextflags)
}
}
} else {
@@ -349,63 +441,30 @@ impl NaiveDate {
/// 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));
+ /// # Panics
///
- /// 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));
- /// ~~~~
+ /// Panics if the date is out of range.
+ #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")]
#[inline]
- pub fn from_num_days_from_ce(days: i32) -> NaiveDate {
- NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date")
+ #[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 = NaiveDate::from_ymd;
+ /// 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)));
@@ -413,57 +472,60 @@ impl NaiveDate {
/// 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<NaiveDate> {
- let days = days + 365; // make December 31, 1 BCE equal to day 0
- let (year_div_400, cycle) = div_mod_floor(days, 146_097);
+ /// ```
+ #[must_use]
+ pub const fn from_num_days_from_ce_opt(days: i32) -> Option<NaiveDate> {
+ 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) = 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))
+ NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, 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
+ /// 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
+ /// `n` is 1-indexed.
///
- /// 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.
+ /// # Panics
///
- /// `n` is 1-indexed. Passing `n=0` will cause a panic.
+ /// 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)`.
///
- /// # Example
+ /// `n` is 1-indexed.
///
- /// ~~~~
- /// use chrono::{NaiveDate, Weekday};
+ /// # Errors
///
- /// let from_weekday_of_month = NaiveDate::from_weekday_of_month;
- /// let from_ymd = NaiveDate::from_ymd;
+ /// 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`.
///
- /// 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.
+ /// # 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))
- /// ~~~~
- ///
- /// 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(
+ /// ```
+ #[must_use]
+ pub const fn from_weekday_of_month_opt(
year: i32,
month: u32,
weekday: Weekday,
@@ -472,76 +534,295 @@ impl NaiveDate {
if n == 0 {
return None;
}
- let first = NaiveDate::from_ymd(year, month, 1).weekday();
+ 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 = (u32::from(n) - 1) * 7 + first_to_dow + 1;
+ 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](../format/strftime/index.html)
+ /// 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(2015, 9, 5)));
+ /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()));
/// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"),
- /// Ok(NaiveDate::from_ymd(2015, 9, 5)));
- /// ~~~~
+ /// 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(2014, 5, 17)));
- /// ~~~~
+ /// 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<NaiveDate> {
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<Self> {
+ if months.0 == 0 {
+ return Some(self);
+ }
+
+ match months.0 <= core::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<Self> {
+ if months.0 == 0 {
+ return Some(self);
+ }
+
+ // Copy `i32::MAX` here so we don't have to do a complicated cast
+ match months.0 <= 2_147_483_647 {
+ true => self.diff_months(-(months.0 as i32)),
+ false => None,
+ }
+ }
+
+ const fn diff_months(self, months: i32) -> Option<Self> {
+ let (years, left) = ((months / 12), (months % 12));
+
+ // Determine new year (without taking months into account for now
+
+ let year = if (years > 0 && years > (MAX_YEAR - self.year()))
+ || (years < 0 && years < (MIN_YEAR - self.year()))
+ {
+ return None;
+ } else {
+ self.year() + years
+ };
+
+ // Determine new month
+
+ let month = self.month() as i32 + left;
+ let (year, month) = if month <= 0 {
+ if year == MIN_YEAR {
+ return None;
+ }
+
+ (year - 1, month + 12)
+ } else if month > 12 {
+ if year == MAX_YEAR {
+ return None;
+ }
+
+ (year + 1, month - 12)
+ } else {
+ (year, month)
+ };
+
+ // 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_mdf(year, try_opt!(Mdf::new(month as u32, day, flags)))
+ }
+
+ /// 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<Self> {
+ 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<Self> {
+ 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<Self> {
+ // fast path if the result is within the same year
+ const ORDINAL_MASK: i32 = 0b1_1111_1111_0000;
+ if let Some(ordinal) = ((self.ymdf & ORDINAL_MASK) >> 4).checked_add(days) {
+ if ordinal > 0 && ordinal <= 365 {
+ let year_and_flags = self.ymdf & !ORDINAL_MASK;
+ return Some(NaiveDate { ymdf: 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 = internals::yo_to_cycle(year_mod_400 as u32, self.of().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) = internals::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, NaiveTime, NaiveDateTime};
///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
- /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789);
+ /// 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]
- pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime {
+ #[must_use]
+ pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime {
NaiveDateTime::new(*self, time)
}
@@ -550,23 +831,14 @@ impl NaiveDate {
/// 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);
+ /// # Panics
///
- /// 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);
- /// ~~~~
+ /// Panics on invalid hour, minute and/or second.
+ #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")]
#[inline]
- pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime {
- self.and_hms_opt(hour, min, sec).expect("invalid time")
+ #[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.
@@ -574,401 +846,402 @@ impl NaiveDate {
/// 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(2015, 6, 3);
+ /// 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]
- pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<NaiveDateTime> {
- NaiveTime::from_hms_opt(hour, min, sec).map(|time| self.and_time(time))
+ #[must_use]
+ pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<NaiveDateTime> {
+ 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 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
+ /// The millisecond 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`.
///
- /// ~~~~
- /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday};
- ///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
+ /// # Panics
///
- /// 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);
- /// ~~~~
+ /// Panics on invalid hour, minute, second and/or millisecond.
+ #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")]
#[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")
+ #[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 can exceed 1,000
- /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
+ /// The millisecond 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 millisecond.
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::NaiveDate;
///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
+ /// 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]
- pub fn and_hms_milli_opt(
+ #[must_use]
+ pub const fn and_hms_milli_opt(
&self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> Option<NaiveDateTime> {
- NaiveTime::from_hms_milli_opt(hour, min, sec, milli).map(|time| self.and_time(time))
+ 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 can exceed 1,000,000
- /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
+ /// The microsecond 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 microsecond.
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday};
///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
+ /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
///
- /// let dt: NaiveDateTime = d.and_hms_micro(12, 34, 56, 789_012);
+ /// 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]
- 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")
+ #[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 can exceed 1,000,000
- /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
+ /// The microsecond 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 microsecond.
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::NaiveDate;
///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
+ /// 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]
- pub fn and_hms_micro_opt(
+ #[must_use]
+ pub const fn and_hms_micro_opt(
&self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> Option<NaiveDateTime> {
- NaiveTime::from_hms_micro_opt(hour, min, sec, micro).map(|time| self.and_time(time))
+ 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 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};
+ /// 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`.
///
- /// let d = NaiveDate::from_ymd(2015, 6, 3);
+ /// # Panics
///
- /// 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);
- /// ~~~~
+ /// Panics on invalid hour, minute, second and/or nanosecond.
+ #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")]
#[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")
+ #[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 can exceed 1,000,000,000
- /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
+ /// 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(2015, 6, 3);
+ /// 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]
- pub fn and_hms_nano_opt(
+ #[must_use]
+ pub const fn and_hms_nano_opt(
&self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> Option<NaiveDateTime> {
- NaiveTime::from_hms_nano_opt(hour, min, sec, nano).map(|time| self.and_time(time))
+ 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]
- fn mdf(&self) -> Mdf {
+ const 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)
+ const fn of(&self) -> Of {
+ Of::from_date_impl(self.ymdf)
}
/// 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<NaiveDate> {
- self.with_of(mdf.to_of())
+ const fn with_mdf(&self, mdf: Mdf) -> Option<NaiveDate> {
+ Some(self.with_of(try_opt!(mdf.to_of())))
}
/// Makes a new `NaiveDate` with the packed ordinal-flags changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// Does not check if the year flags match the year.
#[inline]
- fn with_of(&self, of: Of) -> Option<NaiveDate> {
- if of.valid() {
- let Of(of) = of;
- Some(NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of as DateImpl })
- } else {
- None
- }
+ const fn with_of(&self, of: Of) -> NaiveDate {
+ NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl }
}
/// Makes a new `NaiveDate` for the next calendar date.
///
- /// Panics when `self` is the last representable date.
- ///
- /// # Example
- ///
- /// ~~~~
- /// use chrono::NaiveDate;
+ /// # Panics
///
- /// 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));
- /// ~~~~
+ /// Panics when `self` is the last representable date.
+ #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")]
#[inline]
- pub fn succ(&self) -> NaiveDate {
- self.succ_opt().expect("out of bound")
+ #[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;
- /// 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);
- /// ~~~~
+ /// 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]
- pub fn succ_opt(&self) -> Option<NaiveDate> {
- self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1))
+ #[must_use]
+ pub const fn succ_opt(&self) -> Option<NaiveDate> {
+ match self.of().succ() {
+ Some(of) => Some(self.with_of(of)),
+ None => 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;
+ /// # Panics
///
- /// 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));
- /// ~~~~
+ /// Panics when `self` is the first representable date.
+ #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")]
#[inline]
- pub fn pred(&self) -> NaiveDate {
- self.pred_opt().expect("out of bound")
+ #[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;
- /// 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);
- /// ~~~~
+ /// 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]
- pub fn pred_opt(&self) -> Option<NaiveDate> {
- self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31))
+ #[must_use]
+ pub const fn pred_opt(&self) -> Option<NaiveDate> {
+ match self.of().pred() {
+ Some(of) => Some(self.with_of(of)),
+ None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31),
+ }
}
- /// Adds the `days` part of given `Duration` to the current date.
+ /// Adds the number of whole days in the given `TimeDelta` to the current date.
+ ///
+ /// # Errors
///
- /// Returns `None` when it will result in overflow.
+ /// Returns `None` if the resulting date would be out of range.
///
/// # 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<NaiveDate> {
- 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))
+ /// ```
+ /// use chrono::{TimeDelta, NaiveDate};
+ ///
+ /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap();
+ /// assert_eq!(d.checked_add_signed(TimeDelta::days(40)),
+ /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()));
+ /// assert_eq!(d.checked_add_signed(TimeDelta::days(-40)),
+ /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()));
+ /// assert_eq!(d.checked_add_signed(TimeDelta::days(1_000_000_000)), None);
+ /// assert_eq!(d.checked_add_signed(TimeDelta::days(-1_000_000_000)), None);
+ /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::days(1)), None);
+ /// ```
+ #[must_use]
+ pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option<NaiveDate> {
+ 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 `days` part of given `Duration` from the current date.
+ /// Subtracts the number of whole days in the given `TimeDelta` from the current date.
+ ///
+ /// # Errors
///
- /// Returns `None` when it will result in overflow.
+ /// Returns `None` if the resulting date would be out of range.
///
/// # 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<NaiveDate> {
- 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))
+ /// ```
+ /// use chrono::{TimeDelta, NaiveDate};
+ ///
+ /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap();
+ /// assert_eq!(d.checked_sub_signed(TimeDelta::days(40)),
+ /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()));
+ /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-40)),
+ /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()));
+ /// assert_eq!(d.checked_sub_signed(TimeDelta::days(1_000_000_000)), None);
+ /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-1_000_000_000)), None);
+ /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::days(1)), None);
+ /// ```
+ #[must_use]
+ pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option<NaiveDate> {
+ 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 `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`.
///
/// # Example
///
- /// ~~~~
- /// # extern crate chrono; fn main() {
- /// use chrono::{Duration, NaiveDate};
+ /// ```
+ /// use chrono::{TimeDelta, NaiveDate};
///
- /// let from_ymd = NaiveDate::from_ymd;
+ /// 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)), 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 {
+ /// 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::days(1));
+ /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::days(-1));
+ /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), TimeDelta::days(100));
+ /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), TimeDelta::days(365));
+ /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), TimeDelta::days(365*4 + 1));
+ /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), TimeDelta::days(365*400 + 97));
+ /// ```
+ #[must_use]
+ pub const fn signed_duration_since(self, rhs: NaiveDate) -> 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 = 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),
- )
+ let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal()) as i64;
+ let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal()) as i64;
+ TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2))
+ }
+
+ /// Returns the number of whole years from the given `base` until `self`.
+ ///
+ /// # Errors
+ ///
+ /// Returns `None` if `base < self`.
+ #[must_use]
+ pub const fn years_since(&self, base: Self) -> Option<u32> {
+ 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.
@@ -979,27 +1252,28 @@ impl NaiveDate {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::NaiveDate;
/// use chrono::format::strftime::StrftimeItems;
///
/// let fmt = StrftimeItems::new("%Y-%m-%d");
- /// let d = NaiveDate::from_ymd(2015, 9, 5);
+ /// 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(2015, 9, 5);
+ /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap();
/// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05");
- /// ~~~~
- #[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<I>
where
I: Iterator<Item = B> + Clone,
@@ -1009,7 +1283,7 @@ impl NaiveDate {
}
/// Formats the date with the specified format string.
- /// See the [`format::strftime` module](../format/strftime/index.html)
+ /// See the [`format::strftime` module](crate::format::strftime)
/// on the supported escape sequences.
///
/// This returns a `DelayedFormat`,
@@ -1024,29 +1298,61 @@ impl NaiveDate {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::NaiveDate;
///
- /// let d = NaiveDate::from_ymd(2015, 9, 5);
+ /// 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(2015, 9, 5);
+ /// # 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(any(feature = "alloc", feature = "std", test))]
+ /// ```
+ #[cfg(feature = "alloc")]
#[inline]
+ #[must_use]
pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
self.format_with_items(StrftimeItems::new(fmt))
}
- /// Returns an iterator that steps by days until the last representable date.
+ /// 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<I>
+ where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+ {
+ 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<StrftimeItems<'a>> {
+ self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale)
+ }
+
+ /// Returns an iterator that steps by days across all representable dates.
///
/// # Example
///
@@ -1054,25 +1360,30 @@ impl NaiveDate {
/// # 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),
+ /// 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(2016, 2, 27).iter_days().take(4).enumerate() {
+ /// 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);
+ ///
+ /// for d in NaiveDate::from_ymd_opt(2016, 3, 1).unwrap().iter_days().rev().take(4) {
+ /// count -= 1;
+ /// assert_eq!(d, expected[count]);
+ /// }
/// ```
#[inline]
- pub fn iter_days(&self) -> NaiveDateDaysIterator {
+ pub const fn iter_days(&self) -> NaiveDateDaysIterator {
NaiveDateDaysIterator { value: *self }
}
- /// Returns an iterator that steps by weeks until the last representable date.
+ /// Returns an iterator that steps by weeks across all representable dates.
///
/// # Example
///
@@ -1080,23 +1391,109 @@ impl NaiveDate {
/// # 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),
+ /// 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(2016, 2, 27).iter_weeks().take(4).enumerate() {
+ /// 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);
+ ///
+ /// for d in NaiveDate::from_ymd_opt(2016, 3, 19).unwrap().iter_weeks().rev().take(4) {
+ /// count -= 1;
+ /// assert_eq!(d, expected[count]);
+ /// }
/// ```
#[inline]
- pub fn iter_weeks(&self) -> NaiveDateWeeksIterator {
+ pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator {
NaiveDateWeeksIterator { value: *self }
}
+
+ /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`]
+ /// specified.
+ #[inline]
+ pub const fn week(&self, start: Weekday) -> NaiveWeek {
+ NaiveWeek { date: *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.ymdf & (0b1000) == 0
+ }
+
+ // This duplicates `Datelike::year()`, because trait methods can't be const yet.
+ #[inline]
+ const fn year(&self) -> i32 {
+ self.ymdf >> 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.of().ordinal()
+ }
+
+ // 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()
+ }
+
+ // This duplicates `Datelike::weekday()`, because trait methods can't be const yet.
+ #[inline]
+ const fn weekday(&self) -> Weekday {
+ self.of().weekday()
+ }
+
+ /// 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
+ }
+
+ /// The minimum possible `NaiveDate` (January 1, 262144 BCE).
+ pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ };
+ /// The maximum possible `NaiveDate` (December 31, 262142 CE).
+ pub const MAX: NaiveDate = NaiveDate { ymdf: (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 { ymdf: ((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 { ymdf: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ };
}
impl Datelike for NaiveDate {
@@ -1104,15 +1501,15 @@ impl Datelike for NaiveDate {
///
/// # 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
- /// ~~~~
+ /// 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.ymdf >> 13
+ self.year()
}
/// Returns the month number starting from 1.
@@ -1121,15 +1518,15 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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.mdf().month()
+ self.month()
}
/// Returns the month number starting from 0.
@@ -1138,15 +1535,15 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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.mdf().month() - 1
+ self.month() - 1
}
/// Returns the day of month starting from 1.
@@ -1155,27 +1552,27 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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`](#method.pred),
+ /// 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::{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);
+ /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap();
///
/// // ...is preceded by the last day of the original month
- /// d.pred().day()
+ /// d.pred_opt().unwrap().day()
/// }
///
/// assert_eq!(ndays_in_month(2015, 8), 31);
@@ -1183,10 +1580,10 @@ impl Datelike for NaiveDate {
/// 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()
+ self.day()
}
/// Returns the day of month starting from 0.
@@ -1195,12 +1592,12 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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
@@ -1212,26 +1609,26 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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`](#method.pred),
+ /// 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::{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);
+ /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap();
///
/// // ...is preceded by the last day of the original year
- /// d.pred().ordinal()
+ /// d.pred_opt().unwrap().ordinal()
/// }
///
/// assert_eq!(ndays_in_year(2015), 365);
@@ -1239,7 +1636,7 @@ impl Datelike for NaiveDate {
/// 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()
@@ -1251,12 +1648,12 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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.of().ordinal() - 1
@@ -1266,15 +1663,15 @@ impl Datelike for NaiveDate {
///
/// # 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);
- /// ~~~~
+ /// 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.of().weekday()
+ self.weekday()
}
#[inline]
@@ -1282,28 +1679,31 @@ impl Datelike for NaiveDate {
isoweek::iso_week_from_yof(self.year(), self.of())
}
- /// Makes a new `NaiveDate` with the year number changed.
+ /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day.
///
- /// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or when the `NaiveDate` would be
+ /// out of range.
///
/// # 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)));
- /// ~~~~
+ /// 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 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());
- /// ~~~~
+ /// 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());
+ /// ```
#[inline]
fn with_year(&self, year: i32) -> Option<NaiveDate> {
// we need to operate with `mdf` since we should keep the month and day number as is
@@ -1318,245 +1718,383 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the month number (starting from 1) changed.
///
- /// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `month` is 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
- /// ~~~~
+ /// 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 February 30
+ /// ```
#[inline]
fn with_month(&self, month: u32) -> Option<NaiveDate> {
- self.with_mdf(self.mdf().with_month(month))
+ 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.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `month0` is
+ /// 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
- /// ~~~~
+ /// 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 13
+ /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // no February 30
+ /// ```
#[inline]
fn with_month0(&self, month0: u32) -> Option<NaiveDate> {
- self.with_mdf(self.mdf().with_month(month0 + 1))
+ 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.
///
- /// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `day` is 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),
+ /// 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<NaiveDate> {
- self.with_mdf(self.mdf().with_day(day))
+ 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.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `day0` is 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),
+ /// 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<NaiveDate> {
- self.with_mdf(self.mdf().with_day(day0 + 1))
+ 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.
///
- /// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is
+ /// 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),
+ /// 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(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)));
- /// ~~~~
+ /// 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<NaiveDate> {
- self.with_of(self.of().with_ordinal(ordinal))
+ self.of().with_ordinal(ordinal).map(|of| self.with_of(of))
}
/// Makes a new `NaiveDate` with the day of year (starting from 0) changed.
///
- /// Returns `None` when the resulting `NaiveDate` would be invalid.
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is
+ /// 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),
+ /// 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(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)));
- /// ~~~~
+ /// 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<NaiveDate> {
- self.with_of(self.of().with_ordinal(ordinal0 + 1))
+ let ordinal = ordinal0.checked_add(1)?;
+ self.with_ordinal(ordinal)
}
}
-/// An addition of `Duration` to `NaiveDate` discards the fractional days,
-/// rounding to the closest integral number of days towards `Duration::zero()`.
+/// Add `TimeDelta` to `NaiveDate`.
+///
+/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of
+/// days towards `TimeDelta::zero()`.
///
-/// Panics on underflow or overflow.
-/// Use [`NaiveDate::checked_add_signed`](#method.checked_add_signed) to detect that.
+/// # Panics
+///
+/// Panics if the resulting date would be out of range.
+/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead.
///
/// # 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<OldDuration> for NaiveDate {
+/// ```
+/// use chrono::{TimeDelta, NaiveDate};
+///
+/// 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::seconds(86399), from_ymd(2014, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(1), from_ymd(2014, 1, 2));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(-1), from_ymd(2013, 12, 31));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(364), from_ymd(2014, 12, 31));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*4 + 1), from_ymd(2018, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*400 + 97), from_ymd(2414, 1, 1));
+/// ```
+///
+/// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed
+impl Add<TimeDelta> for NaiveDate {
type Output = NaiveDate;
#[inline]
- fn add(self, rhs: OldDuration) -> NaiveDate {
- self.checked_add_signed(rhs).expect("`NaiveDate + Duration` overflowed")
+ fn add(self, rhs: TimeDelta) -> NaiveDate {
+ self.checked_add_signed(rhs).expect("`NaiveDate + TimeDelta` overflowed")
}
}
-impl AddAssign<OldDuration> for NaiveDate {
+/// 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<TimeDelta> for NaiveDate {
#[inline]
- fn add_assign(&mut self, rhs: OldDuration) {
+ fn add_assign(&mut self, rhs: TimeDelta) {
*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`.
+/// 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::{NaiveDate, Months};
+///
+/// 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<Months> for NaiveDate {
+ type Output = NaiveDate;
+
+ 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 on underflow or overflow.
-/// Use [`NaiveDate::checked_sub_signed`](#method.checked_sub_signed) to detect that.
+/// Panics if the resulting date would be out of range.
+/// Consider using `NaiveDate::checked_sub_months` to get an `Option` instead.
///
/// # 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<OldDuration> for NaiveDate {
+/// ```
+/// use chrono::{NaiveDate, Months};
+///
+/// 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<Months> for NaiveDate {
+ type Output = NaiveDate;
+
+ 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<Days> for NaiveDate {
+ type Output = NaiveDate;
+
+ 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<Days> for NaiveDate {
+ type Output = NaiveDate;
+
+ 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::{TimeDelta, NaiveDate};
+///
+/// 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::seconds(86399), from_ymd(2014, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(1), from_ymd(2013, 12, 31));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(-1), from_ymd(2014, 1, 2));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(364), from_ymd(2013, 1, 2));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*4 + 1), from_ymd(2010, 1, 1));
+/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*400 + 97), from_ymd(1614, 1, 1));
+/// ```
+///
+/// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed
+impl Sub<TimeDelta> for NaiveDate {
type Output = NaiveDate;
#[inline]
- fn sub(self, rhs: OldDuration) -> NaiveDate {
- self.checked_sub_signed(rhs).expect("`NaiveDate - Duration` overflowed")
+ fn sub(self, rhs: TimeDelta) -> NaiveDate {
+ self.checked_sub_signed(rhs).expect("`NaiveDate - TimeDelta` overflowed")
}
}
-impl SubAssign<OldDuration> for NaiveDate {
+/// 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<TimeDelta> for NaiveDate {
#[inline]
- fn sub_assign(&mut self, rhs: OldDuration) {
+ fn sub_assign(&mut self, rhs: TimeDelta) {
*self = self.sub(rhs);
}
}
/// Subtracts another `NaiveDate` 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`.
///
/// 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));
-/// # }
-/// ~~~~
+/// ```
+/// use chrono::{TimeDelta, NaiveDate};
+///
+/// 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::days(1));
+/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::days(-1));
+/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::days(100));
+/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::days(365));
+/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), TimeDelta::days(365*4 + 1));
+/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), TimeDelta::days(365*400 + 97));
+/// ```
impl Sub<NaiveDate> for NaiveDate {
- type Output = OldDuration;
+ type Output = TimeDelta;
#[inline]
- fn sub(self, rhs: NaiveDate) -> OldDuration {
+ fn sub(self, rhs: NaiveDate) -> TimeDelta {
self.signed_duration_since(rhs)
}
}
+impl From<NaiveDateTime> 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 {
@@ -1567,24 +2105,33 @@ impl Iterator for NaiveDateDaysIterator {
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
- if self.value == MAX_DATE {
- return None;
- }
- // current < MAX_DATE from here on:
+ // We return the current value, and have no way to return `NaiveDate::MAX`.
let current = self.value;
- // This can't panic because current is < MAX_DATE:
- self.value = current.succ();
+ // This can't panic because current is < NaiveDate::MAX:
+ self.value = current.succ_opt()?;
Some(current)
}
fn size_hint(&self) -> (usize, Option<usize>) {
- let exact_size = MAX_DATE.signed_duration_since(self.value).num_days();
+ let exact_size = NaiveDate::MAX.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<Self::Item> {
+ // We return the current value, and have no way to return `NaiveDate::MIN`.
+ let current = self.value;
+ self.value = current.pred_opt()?;
+ Some(current)
+ }
+}
+
+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,
@@ -1594,83 +2141,94 @@ impl Iterator for NaiveDateWeeksIterator {
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
- if MAX_DATE - self.value < OldDuration::weeks(1) {
- return None;
- }
let current = self.value;
- self.value = current + OldDuration::weeks(1);
+ self.value = current.checked_add_signed(TimeDelta::weeks(1))?;
Some(current)
}
fn size_hint(&self) -> (usize, Option<usize>) {
- let exact_size = MAX_DATE.signed_duration_since(self.value).num_weeks();
+ let exact_size = NaiveDate::MAX.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
+impl DoubleEndedIterator for NaiveDateWeeksIterator {
+ fn next_back(&mut self) -> Option<Self::Item> {
+ let current = self.value;
+ self.value = current.checked_sub_signed(TimeDelta::weeks(1))?;
+ Some(current)
+ }
+}
+
+impl FusedIterator for NaiveDateWeeksIterator {}
/// The `Debug` output of the naive date `d` is the same as
-/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html).
+/// [`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(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");
-/// ~~~~
+/// 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( -1, 1, 1)), "-0001-01-01");
-/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31");
-/// ~~~~
+/// 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 <= year && year <= 9999 {
- write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day())
+ 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, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day())
+ write!(f, "{:+05}", year)?;
}
+
+ f.write_char('-')?;
+ write_hundreds(f, mdf.month() as u8)?;
+ f.write_char('-')?;
+ write_hundreds(f, mdf.day() as u8)
}
}
/// The `Display` output of the naive date `d` is the same as
-/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html).
+/// [`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(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");
-/// ~~~~
+/// 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( -1, 1, 1)), "-0001-01-01");
-/// assert_eq!(format!("{}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31");
-/// ~~~~
+/// 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)
@@ -1678,26 +2236,26 @@ impl fmt::Display for NaiveDate {
}
/// Parsing a `str` into a `NaiveDate` uses the same format,
-/// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`.
+/// [`%Y-%m-%d`](crate::format::strftime), as in `Debug` and `Display`.
///
/// # Example
///
-/// ~~~~
+/// ```
/// use chrono::NaiveDate;
///
-/// let d = NaiveDate::from_ymd(2015, 9, 18);
+/// let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap();
/// assert_eq!("2015-09-18".parse::<NaiveDate>(), Ok(d));
///
-/// let d = NaiveDate::from_ymd(12345, 6, 7);
+/// let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap();
/// assert_eq!("+12345-6-7".parse::<NaiveDate>(), Ok(d));
///
/// assert!("foo".parse::<NaiveDate>().is_err());
-/// ~~~~
+/// ```
impl str::FromStr for NaiveDate {
type Err = ParseError;
fn from_str(s: &str) -> ParseResult<NaiveDate> {
- const ITEMS: &'static [Item<'static>] = &[
+ const ITEMS: &[Item<'static>] = &[
Item::Numeric(Numeric::Year, Pad::Zero),
Item::Space(""),
Item::Literal("-"),
@@ -1714,17 +2272,46 @@ impl str::FromStr for NaiveDate {
}
}
+/// 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 div_mod_floor(val: i32, div: i32) -> (i32, i32) {
+ (val.div_euclid(div), val.rem_euclid(div))
+}
+
#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
fn test_encodable_json<F, E>(to_string: F)
where
F: Fn(&NaiveDate) -> Result<String, E>,
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()));
+ assert_eq!(
+ to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(),
+ Some(r#""2014-07-24""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(),
+ Some(r#""0000-01-01""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(),
+ Some(r#""-0001-12-31""#.into())
+ );
+ assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262143-01-01""#.into()));
+ assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262142-12-31""#.into()));
}
#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
@@ -1735,14 +2322,20 @@ where
{
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));
+ 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());
@@ -1782,16 +2375,19 @@ mod rustc_serialize {
}
#[cfg(test)]
- use rustc_serialize::json;
+ mod tests {
+ use crate::naive::date::{test_decodable_json, test_encodable_json};
+ use rustc_serialize::json;
- #[test]
- fn test_encodable() {
- super::test_encodable_json(json::encode);
- }
+ #[test]
+ fn test_encodable() {
+ test_encodable_json(json::encode);
+ }
- #[test]
- fn test_decodable() {
- super::test_decodable_json(json::decode);
+ #[test]
+ fn test_decodable() {
+ test_decodable_json(json::decode);
+ }
}
}
@@ -1799,7 +2395,7 @@ mod rustc_serialize {
mod serde {
use super::NaiveDate;
use core::fmt;
- use serdelib::{de, ser};
+ use serde::{de, ser};
// TODO not very optimized for space (binary formats would want something better)
@@ -1828,19 +2424,10 @@ mod serde {
type Value = NaiveDate;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- write!(formatter, "a formatted date string")
+ formatter.write_str("a formatted date string")
}
- #[cfg(any(feature = "std", test))]
- fn visit_str<E>(self, value: &str) -> Result<NaiveDate, E>
- where
- E: de::Error,
- {
- value.parse().map_err(E::custom)
- }
-
- #[cfg(not(any(feature = "std", test)))]
- fn visit_str<E>(self, value: &str) -> Result<NaiveDate, E>
+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
@@ -1858,45 +2445,190 @@ mod serde {
}
#[cfg(test)]
- extern crate bincode;
- #[cfg(test)]
- extern crate serde_json;
+ mod tests {
+ use crate::naive::date::{test_decodable_json, test_encodable_json};
+ use crate::NaiveDate;
- #[test]
- fn test_serde_serialize() {
- super::test_encodable_json(self::serde_json::to_string);
+ #[test]
+ fn test_serde_serialize() {
+ test_encodable_json(serde_json::to_string);
+ }
+
+ #[test]
+ fn test_serde_deserialize() {
+ test_decodable_json(|input| 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 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);
+ }
}
+}
+#[cfg(test)]
+mod tests {
+ use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR};
+ use crate::naive::internals::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_serde_deserialize() {
- super::test_decodable_json(|input| self::serde_json::from_str(&input));
+ 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.of().flags()
+ );
+ assert!(
+ NaiveDate::MAX == calculated_max,
+ "`NaiveDate::MAX` should have year flag {:?} and ordinal {}",
+ calculated_max.of().flags(),
+ calculated_max.of().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^{} seconds",
+ MAX_BITS
+ );
+
+ const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN;
+ assert_eq!(BEFORE_MIN.of().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.of().flags(), YearFlags::from_year(AFTER_MAX.year()));
+ assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1));
}
#[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};
+ 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())
+ );
- let d = NaiveDate::from_ymd(2014, 7, 24);
- let encoded = serialize(&d, Infinite).unwrap();
- let decoded: NaiveDate = deserialize(&encoded).unwrap();
- assert_eq!(d, decoded);
+ // 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())
+ );
}
-}
-#[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_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 ymd_opt = |y, m, d| NaiveDate::from_ymd_opt(y, m, d);
+ let ymd_opt = NaiveDate::from_ymd_opt;
assert!(ymd_opt(2012, 0, 1).is_none());
assert!(ymd_opt(2012, 1, 1).is_some());
@@ -1912,8 +2644,8 @@ mod tests {
#[test]
fn test_date_from_yo() {
- let yo_opt = |y, o| NaiveDate::from_yo_opt(y, o);
- let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d);
+ let yo_opt = NaiveDate::from_yo_opt;
+ let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(yo_opt(2012, 0), None);
assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1)));
@@ -1942,8 +2674,8 @@ mod tests {
#[test]
fn test_date_from_isoywd() {
- let isoywd_opt = |y, w, d| NaiveDate::from_isoywd_opt(y, w, d);
- let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d);
+ let isoywd_opt = NaiveDate::from_isoywd_opt;
+ let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None);
assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29)));
@@ -1985,8 +2717,7 @@ mod tests {
.iter()
{
let d = NaiveDate::from_isoywd_opt(year, week, weekday);
- if d.is_some() {
- let d = d.unwrap();
+ if let Some(d) = d {
assert_eq!(d.weekday(), weekday);
let w = d.iso_week();
assert_eq!(w.year(), year);
@@ -2000,11 +2731,10 @@ mod tests {
for month in 1..13 {
for day in 1..32 {
let d = NaiveDate::from_ymd_opt(year, month, day);
- if d.is_some() {
- let d = d.unwrap();
+ 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_);
+ let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday());
+ assert_eq!(d, d_.unwrap());
}
}
}
@@ -2013,63 +2743,114 @@ mod tests {
#[test]
fn test_date_from_num_days_from_ce() {
- let from_ndays_from_ce = |days| NaiveDate::from_num_days_from_ce_opt(days);
- 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 + 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
+ 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(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);
+ 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_weekday_of_month_opt() {
- let ymwd = |y, m, w, n| NaiveDate::from_weekday_of_month_opt(y, m, w, n);
+ 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::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(year, month, day);
+ 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(year, 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);
@@ -2101,168 +2882,216 @@ mod tests {
#[test]
fn test_date_weekday() {
- assert_eq!(NaiveDate::from_ymd(1582, 10, 15).weekday(), Weekday::Fri);
+ 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(1875, 5, 20).weekday(), Weekday::Thu);
- assert_eq!(NaiveDate::from_ymd(2000, 1, 1).weekday(), Weekday::Sat);
+ 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(2000, 2, 29);
- assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd(-400, 2, 29)));
+ 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(1600, 2, 29)));
+ 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(2000, 2, 29)));
+ 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(2004, 2, 29)));
+ 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(2000, 4, 30);
+ 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(2000, 1, 30)));
+ 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(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(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(2000, 2, 8);
+ 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(2000, 2, 1)));
- assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd(2000, 2, 29)));
+ 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);
- let d = NaiveDate::from_ymd(2000, 5, 5);
+ 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(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(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(u32::MAX), None);
}
#[test]
fn test_date_num_days_from_ce() {
- assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1);
+ 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(year, 1, 1).num_days_from_ce(),
- NaiveDate::from_ymd(year - 1, 12, 31).num_days_from_ce() + 1
+ 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_succ() {
- let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d);
+ 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(MAX_DATE.year(), 12, 31).succ_opt(), None);
+ assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None);
}
#[test]
fn test_date_pred() {
- let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d);
+ 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(MIN_DATE.year(), 1, 1).pred_opt(), None);
+ assert_eq!(ymd(NaiveDate::MIN.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));
+ fn check((y1, m1, d1): (i32, u32, u32), rhs: TimeDelta, ymd: Option<(i32, u32, u32)>) {
+ let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap();
+ let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap());
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)));
+ check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1)));
+ check((2014, 1, 1), TimeDelta::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((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1)));
+ check((2014, 1, 1), TimeDelta::days(1), Some((2014, 1, 2)));
+ check((2014, 1, 1), TimeDelta::days(-1), Some((2013, 12, 31)));
+ check((2014, 1, 1), TimeDelta::days(364), Some((2014, 12, 31)));
+ check((2014, 1, 1), TimeDelta::days(365 * 4 + 1), Some((2018, 1, 1)));
+ check((2014, 1, 1), TimeDelta::days(365 * 400 + 97), Some((2414, 1, 1)));
- check((-7, 1, 1), Duration::days(365 * 12 + 3), Some((5, 1, 1)));
+ check((-7, 1, 1), TimeDelta::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);
+ check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31)));
+ check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None);
+ check((0, 1, 1), TimeDelta::max_value(), None);
+ check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1)));
+ check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None);
+ check((0, 1, 1), TimeDelta::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);
+ fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: TimeDelta) {
+ let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap();
+ let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap();
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((2014, 1, 1), (2014, 1, 1), TimeDelta::zero());
+ check((2014, 1, 2), (2014, 1, 1), TimeDelta::days(1));
+ check((2014, 12, 31), (2014, 1, 1), TimeDelta::days(364));
+ check((2015, 1, 3), (2014, 1, 1), TimeDelta::days(365 + 2));
+ check((2018, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 4 + 1));
+ check((2414, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 400 + 97));
+
+ check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64));
+ check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64));
+ }
+
+ #[test]
+ fn test_date_add_days() {
+ fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) {
+ let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap();
+ let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap());
+ assert_eq!(lhs.checked_add_days(rhs), sum);
+ }
+
+ check((2014, 1, 1), Days::new(0), Some((2014, 1, 1)));
+ // always round towards zero
+ check((2014, 1, 1), Days::new(1), Some((2014, 1, 2)));
+ check((2014, 1, 1), Days::new(364), Some((2014, 12, 31)));
+ check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1)));
+ check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1)));
+
+ check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1)));
+
+ // overflow check
+ check(
+ (0, 1, 1),
+ Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()),
+ Some((MAX_YEAR, 12, 31)),
+ );
+ check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None);
+ }
+
+ #[test]
+ fn test_date_sub_days() {
+ fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) {
+ let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap();
+ let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap();
+ assert_eq!(lhs - diff, rhs);
+ }
+
+ check((2014, 1, 1), (2014, 1, 1), Days::new(0));
+ check((2014, 1, 2), (2014, 1, 1), Days::new(1));
+ check((2014, 12, 31), (2014, 1, 1), Days::new(364));
+ check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2));
+ check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1));
+ check((2414, 1, 1), (2014, 1, 1), Days::new(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));
+ check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()));
+ check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()));
}
#[test]
fn test_date_addassignment() {
- let ymd = NaiveDate::from_ymd;
+ let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
let mut date = ymd(2016, 10, 1);
- date += Duration::days(10);
+ date += TimeDelta::days(10);
assert_eq!(date, ymd(2016, 10, 11));
- date += Duration::days(30);
+ date += TimeDelta::days(30);
assert_eq!(date, ymd(2016, 11, 10));
}
#[test]
fn test_date_subassignment() {
- let ymd = NaiveDate::from_ymd;
+ let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
let mut date = ymd(2016, 10, 11);
- date -= Duration::days(10);
+ date -= TimeDelta::days(10);
assert_eq!(date, ymd(2016, 10, 1));
- date -= Duration::days(2);
+ date -= TimeDelta::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!(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(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");
+ 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(1234, 5, 6)), "1234-05-06");
- assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07");
+ 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]
@@ -2278,16 +3107,20 @@ mod tests {
"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::<NaiveDate>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
+ eprintln!("d {:?} (NaiveDate)", d);
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::<NaiveDate>() {
Ok(d) => d,
@@ -2295,6 +3128,7 @@ mod tests {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
+ eprintln!("d_ {:?} (NaiveDate)", d_);
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
@@ -2307,18 +3141,32 @@ mod tests {
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
- assert!("".parse::<NaiveDate>().is_err());
- assert!("x".parse::<NaiveDate>().is_err());
- assert!("2014".parse::<NaiveDate>().is_err());
- assert!("2014-01".parse::<NaiveDate>().is_err());
- assert!("2014-01-00".parse::<NaiveDate>().is_err());
- assert!("2014-13-57".parse::<NaiveDate>().is_err());
- assert!("9999999-9-9".parse::<NaiveDate>().is_err()); // out-of-bounds
+ 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::<NaiveDate>().is_err());
+ }
}
#[test]
fn test_date_parse_from_str() {
- let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d);
+ 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))
@@ -2334,50 +3182,26 @@ mod tests {
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"
+ NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
+ NaiveDate::from_ymd_opt(2020, 1, 12),
);
+
assert_eq!(
- NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(),
- "2009,09,01,00,53"
+ NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
+ NaiveDate::from_ymd_opt(2019, 1, 13),
);
}
#[test]
fn test_day_iterator_limit() {
assert_eq!(
- NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).collect::<Vec<_>>().len(),
+ NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(),
+ 2
+ );
+ assert_eq!(
+ NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(),
2
);
}
@@ -2385,8 +3209,149 @@ mod tests {
#[test]
fn test_week_iterator_limit() {
assert_eq!(
- NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).collect::<Vec<_>>().len(),
+ NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(),
+ 2
+ );
+ assert_eq!(
+ NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(),
2
);
}
+
+ #[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_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]
+ #[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::<NaiveDate>(&bytes).unwrap(), date_min);
+
+ let date_max = NaiveDate::MAX;
+ let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap();
+ assert_eq!(rkyv::from_bytes::<NaiveDate>(&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 92d6c28..0000000
--- a/src/naive/datetime.rs
+++ /dev/null
@@ -1,2507 +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<NaiveDateTime> {
- 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<NaiveDateTime> {
- 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 *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<NaiveDateTime> {
- 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<NaiveDateTime> {
- 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<I>
- where
- I: Iterator<Item = B> + Clone,
- B: Borrow<Item<'a>>,
- {
- 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<StrftimeItems<'a>> {
- 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`](./struct.NaiveDate.html#method.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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveDate.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveTime.html#method.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`](./struct.NaiveTime.html#method.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`](./struct.NaiveTime.html#method.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`](./struct.NaiveTime.html#method.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`](./struct.NaiveTime.html#method.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<NaiveDateTime> {
- 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`](./struct.NaiveTime.html#method.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<NaiveDateTime> {
- 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 [`second`](#method.second) method,
- /// the input range is restricted to 0 through 59.
- ///
- /// See also the
- /// [`NaiveTime::with_second`](./struct.NaiveTime.html#method.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<NaiveDateTime> {
- 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 [`nanosecond`](#method.nanosecond) method,
- /// the input range can exceed 1,000,000,000 for leap seconds.
- ///
- /// See also the
- /// [`NaiveTime::with_nanosecond`](./struct.NaiveTime.html#method.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<NaiveDateTime> {
- 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<H: hash::Hasher>(&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`](#method.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<OldDuration> for NaiveDateTime {
- type Output = NaiveDateTime;
-
- #[inline]
- fn add(self, rhs: OldDuration) -> NaiveDateTime {
- self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed")
- }
-}
-
-impl AddAssign<OldDuration> 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`](#method.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<OldDuration> for NaiveDateTime {
- type Output = NaiveDateTime;
-
- #[inline]
- fn sub(self, rhs: OldDuration) -> NaiveDateTime {
- self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed")
- }
-}
-
-impl SubAssign<OldDuration> 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`](#method.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
-/// 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 - 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<NaiveDateTime> 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/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::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/index.html).
-///
-/// 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/index.html), 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::<NaiveDateTime>(), 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::<NaiveDateTime>(), Ok(dt));
-///
-/// assert!("foo".parse::<NaiveDateTime>().is_err());
-/// ~~~~
-impl str::FromStr for NaiveDateTime {
- type Err = ParseError;
-
- fn from_str(s: &str) -> ParseResult<NaiveDateTime> {
- 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)
- }
-}
-
-#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
-fn test_encodable_json<F, E>(to_string: F)
-where
- F: Fn(&NaiveDateTime) -> Result<String, E>,
- E: ::std::fmt::Debug,
-{
- use naive::{MAX_DATE, MIN_DATE};
-
- 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<F, E>(from_str: F)
-where
- F: Fn(&str) -> Result<NaiveDateTime, E>,
- E: ::std::fmt::Debug,
-{
- use naive::{MAX_DATE, MIN_DATE};
-
- 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<F, E>(from_str: F)
-where
- F: Fn(&str) -> Result<rustc_serialize::TsSeconds, E>,
- 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<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
- format!("{:?}", self).encode(s)
- }
- }
-
- impl Decodable for NaiveDateTime {
- fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
- 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<TsSeconds> 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: Decoder>(d: &mut D) -> Result<TsSeconds, D::Error> {
- 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- 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 {
- write!(formatter, "a formatted date and time string")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<NaiveDateTime, E>
- where
- E: de::Error,
- {
- value.parse().map_err(E::custom)
- }
- }
-
- impl<'de> de::Deserialize<'de> for NaiveDateTime {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- 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<S, serde_json::Error> {
- /// 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<String, serde_json::Error> {
- /// 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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
- 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<S, serde_json::Error> {
- /// 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<NaiveDateTime, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(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<E>(self, value: i64) -> Result<NaiveDateTime, E>
- 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<E>(self, value: u64) -> Result<NaiveDateTime, E>
- 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<S, serde_json::Error> {
- /// 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<String, serde_json::Error> {
- /// 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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
- 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<S, serde_json::Error> {
- /// 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<NaiveDateTime, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(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<E>(self, value: i64) -> Result<NaiveDateTime, E>
- 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<E>(self, value: u64) -> Result<NaiveDateTime, E>
- 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<S, serde_json::Error> {
- /// 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<String, serde_json::Error> {
- /// 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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
- 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<S, serde_json::Error> {
- /// 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<NaiveDateTime, D::Error>
- where
- D: de::Deserializer<'de>,
- {
- Ok(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<E>(self, value: i64) -> Result<NaiveDateTime, E>
- where
- E: de::Error,
- {
- NaiveDateTime::from_timestamp_opt(value, 0)
- .ok_or_else(|| E::custom(ne_timestamp(value)))
- }
-
- fn visit_u64<E>(self, value: u64) -> Result<NaiveDateTime, E>
- 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<i64>,
- #[serde(with = "ts_nanoseconds_option")]
- two: Option<DateTime<Utc>>,
- }
-
- let expected = Test { one: Some(1), two: Some(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1)) };
- let bytes: Vec<u8> = serialize(&expected, Infinite).unwrap();
- let actual = deserialize::<Test>(&(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::<NaiveDateTime>() {
- 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::<NaiveDateTime>() {
- 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::<NaiveDateTime>().is_err());
- assert!("x".parse::<NaiveDateTime>().is_err());
- assert!("15".parse::<NaiveDateTime>().is_err());
- assert!("15:8:9".parse::<NaiveDateTime>().is_err());
- assert!("15-8-9".parse::<NaiveDateTime>().is_err());
- assert!("2015-15-15T15:15:15".parse::<NaiveDateTime>().is_err());
- assert!("2012-12-12T12:12:12x".parse::<NaiveDateTime>().is_err());
- assert!("2012-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
- assert!("+ 82701-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
- assert!("+802701-123-12T12:12:12".parse::<NaiveDateTime>().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 0000000..d767483
--- /dev/null
+++ b/src/naive/datetime/mod.rs
@@ -0,0 +1,2309 @@
+// 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::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems};
+use crate::format::{Fixed, Item, Numeric, Pad};
+use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
+use crate::offset::Utc;
+use crate::time_delta::NANOS_PER_SEC;
+use crate::{
+ expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone,
+ Timelike, Weekday,
+};
+#[cfg(feature = "rustc-serialize")]
+pub(super) mod rustc_serialize;
+
+/// Tools to help serializing/deserializing `NaiveDateTime`s
+#[cfg(feature = "serde")]
+pub(crate) mod serde;
+
+#[cfg(test)]
+mod tests;
+
+/// The tight upper bound guarantees that a time delta with `|TimeDelta| >= 2^MAX_SECS_BITS`
+/// will always overflow the addition with any date and time type.
+///
+/// So why is this needed? `TimeDelta::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`.
+#[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, NaiveTime, NaiveDateTime};
+ ///
+ /// 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 `from_timestamp_opt()` instead")]
+ #[inline]
+ #[must_use]
+ pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime {
+ let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs);
+ expect!(datetime, "invalid or out-of-range datetime")
+ }
+
+ /// 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)
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::NaiveDateTime;
+ /// let timestamp_millis: i64 = 1662921288000; //Sunday, September 11, 2022 6:34:48 PM
+ /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis());
+ ///
+ /// // Negative timestamps (before the UNIX epoch) are supported as well.
+ /// let timestamp_millis: i64 = -2208936075000; //Mon Jan 01 1900 14:38:45 GMT+0000
+ /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn from_timestamp_millis(millis: i64) -> Option<NaiveDateTime> {
+ let secs = millis.div_euclid(1000);
+ let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000;
+ NaiveDateTime::from_timestamp_opt(secs, nsecs)
+ }
+
+ /// 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)
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::NaiveDateTime;
+ /// let timestamp_micros: i64 = 1662921288000000; //Sunday, September 11, 2022 6:34:48 PM
+ /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros());
+ ///
+ /// // Negative timestamps (before the UNIX epoch) are supported as well.
+ /// let timestamp_micros: i64 = -2208936075000000; //Mon Jan 01 1900 14:38:45 GMT+0000
+ /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn from_timestamp_micros(micros: i64) -> Option<NaiveDateTime> {
+ let secs = micros.div_euclid(1_000_000);
+ let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000;
+ NaiveDateTime::from_timestamp_opt(secs, nsecs)
+ }
+
+ /// 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)
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::NaiveDateTime;
+ /// let timestamp_nanos: i64 = 1662921288_000_000_000; //Sunday, September 11, 2022 6:34:48 PM
+ /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap());
+ ///
+ /// // Negative timestamps (before the UNIX epoch) are supported as well.
+ /// let timestamp_nanos: i64 = -2208936075_000_000_000; //Mon Jan 01 1900 14:38:45 GMT+0000
+ /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos);
+ /// assert!(naive_datetime.is_some());
+ /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn from_timestamp_nanos(nanos: i64) -> Option<NaiveDateTime> {
+ let secs = nanos.div_euclid(NANOS_PER_SEC as i64);
+ let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32;
+
+ NaiveDateTime::from_timestamp_opt(secs, nsecs)
+ }
+
+ /// 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).
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::NaiveDateTime;
+ /// 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_none()); // invalid leap second
+ /// assert!(from_timestamp_opt(59, 1_500_000_000).is_some()); // leap second
+ /// assert!(from_timestamp_opt(59, 2_000_000_000).is_none());
+ /// assert!(from_timestamp_opt(i64::MAX, 0).is_none());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option<NaiveDateTime> {
+ let days = secs.div_euclid(86_400);
+ let secs = secs.rem_euclid(86_400);
+ if days < i32::MIN as i64 || days > i32::MAX as i64 {
+ return None;
+ }
+ let date =
+ NaiveDate::from_num_days_from_ce_opt(try_opt!((days as i32).checked_add(719_163)));
+ let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs);
+ match (date, time) {
+ (Some(date), Some(time)) => Some(NaiveDateTime { date, time }),
+ (_, _) => None,
+ }
+ }
+
+ /// 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::{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_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<NaiveDateTime> {
+ 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.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::NaiveDate;
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 980).unwrap();
+ /// assert_eq!(dt.timestamp(), 1);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap();
+ /// assert_eq!(dt.timestamp(), 1_000_000_000);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap();
+ /// assert_eq!(dt.timestamp(), -1);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
+ /// assert_eq!(dt.timestamp(), -62198755200);
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn timestamp(&self) -> i64 {
+ const UNIX_EPOCH_DAY: i64 = 719_163;
+ let gregorian_day = self.date.num_days_from_ce() as i64;
+ let seconds_from_midnight = self.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 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_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).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();
+ /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_milli_opt(23, 59, 59, 100).unwrap();
+ /// assert_eq!(dt.timestamp_millis(), -900);
+ /// ```
+ #[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 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_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).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();
+ /// 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 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 `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 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.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime};
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).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();
+ ///
+ /// const A_BILLION: i64 = 1_000_000_000;
+ /// let nanos = dt.timestamp_nanos_opt().unwrap();
+ /// assert_eq!(nanos, 1_000_000_000_000_000_555);
+ /// assert_eq!(
+ /// Some(dt),
+ /// NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32)
+ /// );
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn timestamp_nanos_opt(&self) -> Option<i64> {
+ let mut timestamp = self.timestamp();
+ let mut timestamp_subsec_nanos = self.timestamp_subsec_nanos() as i64;
+
+ // subsec nanos are always non-negative, however the timestamp itself (both in seconds and in nanos) can be
+ // negative. Now i64::MIN is NOT dividable by 1_000_000_000, so
+ //
+ // (timestamp * 1_000_000_000) + nanos
+ //
+ // may underflow (even when in theory we COULD represent the datetime as i64) because we add the non-negative
+ // nanos AFTER the multiplication. This is fixed by converting the negative case to
+ //
+ // ((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000)
+ //
+ // Also see <https://github.com/chronotope/chrono/issues/1289>.
+ if timestamp < 0 && timestamp_subsec_nanos > 0 {
+ timestamp_subsec_nanos -= 1_000_000_000;
+ timestamp += 1;
+ }
+
+ try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(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_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_millis(), 123);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_millis(), 1_234);
+ /// ```
+ #[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 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_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_micros(), 123_456);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567);
+ /// ```
+ #[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 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_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789);
+ ///
+ /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap();
+ /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890);
+ /// ```
+ #[inline]
+ #[must_use]
+ pub const fn timestamp_subsec_nanos(&self) -> u32 {
+ self.time.nanosecond()
+ }
+
+ /// 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::{TimeDelta, NaiveDate};
+ ///
+ /// 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::seconds(1)),
+ /// Some(hms(3, 5, 8)));
+ /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1)),
+ /// Some(hms(3, 5, 6)));
+ /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60)),
+ /// Some(hms(4, 6, 7)));
+ /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400)),
+ /// 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::milliseconds(450)),
+ /// 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::days(1_000_000_000)), 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::milliseconds(-500)),
+ /// Some(hmsm(3, 5, 59, 800)));
+ /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(500)),
+ /// Some(hmsm(3, 5, 59, 1_800)));
+ /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(800)),
+ /// Some(hmsm(3, 6, 0, 100)));
+ /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(10)),
+ /// Some(hmsm(3, 6, 9, 300)));
+ /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(-10)),
+ /// Some(hmsm(3, 5, 50, 300)));
+ /// assert_eq!(leap.checked_add_signed(TimeDelta::days(1)),
+ /// 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<NaiveDateTime> {
+ let (time, rhs) = self.time.overflowing_add_signed(rhs);
+
+ // early checking to avoid overflow in TimeDelta::seconds
+ if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) {
+ return None;
+ }
+
+ let date = try_opt!(self.date.checked_add_signed(TimeDelta::seconds(rhs)));
+ 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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::{TimeDelta, NaiveDate};
+ ///
+ /// 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::seconds(1)),
+ /// Some(hms(3, 5, 6)));
+ /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1)),
+ /// Some(hms(3, 5, 8)));
+ /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60)),
+ /// Some(hms(2, 4, 7)));
+ /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400)),
+ /// 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::milliseconds(670)),
+ /// 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::days(1_000_000_000)), 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::milliseconds(200)),
+ /// Some(hmsm(3, 5, 59, 1_100)));
+ /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(500)),
+ /// Some(hmsm(3, 5, 59, 800)));
+ /// assert_eq!(leap.checked_sub_signed(TimeDelta::seconds(60)),
+ /// Some(hmsm(3, 5, 0, 300)));
+ /// assert_eq!(leap.checked_sub_signed(TimeDelta::days(1)),
+ /// 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<NaiveDateTime> {
+ let (time, rhs) = self.time.overflowing_sub_signed(rhs);
+
+ // early checking to avoid overflow in TimeDelta::seconds
+ if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) {
+ return None;
+ }
+
+ let date = try_opt!(self.date.checked_sub_signed(TimeDelta::seconds(rhs)));
+ 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<NaiveDateTime> {
+ 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<Self> {
+ 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<Self> {
+ 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::{TimeDelta, NaiveDate};
+ ///
+ /// 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::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_opt(0, 7, 6, 500).unwrap().signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()),
+ /// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500));
+ /// ```
+ ///
+ /// 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::seconds(3600) + TimeDelta::milliseconds(500));
+ /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap),
+ /// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500));
+ /// ```
+ #[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::NaiveDate;
+ /// use chrono::format::strftime::StrftimeItems;
+ ///
+ /// 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<I>
+ where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+ {
+ 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<StrftimeItems<'a>> {
+ self.format_with_items(StrftimeItems::new(fmt))
+ }
+
+ /// Converts the `NaiveDateTime` into the timezone-aware `DateTime<Tz>`
+ /// with the provided timezone, if possible.
+ ///
+ /// This can fail in cases where the local time represented by the `NaiveDateTime`
+ /// is not a valid local timestamp in the target timezone due to an offset transition
+ /// for example if the target timezone had a change from +00:00 to +01:00
+ /// occuring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04
+ /// could never occur. Similarly, if the offset transitioned in the opposite direction
+ /// then there would be two local times of 2015-09-05 23:56:04, one at +00:00 and one
+ /// at +01:00.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, FixedOffset};
+ /// 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<Tz: TimeZone>(&self, tz: Tz) -> LocalResult<DateTime<Tz>> {
+ tz.from_local_datetime(self)
+ }
+
+ /// Converts the `NaiveDateTime` into the timezone-aware `DateTime<Utc>`.
+ ///
+ /// # 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<Utc> {
+ 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 Unix Epoch, 1970-01-01 00:00:00.
+ pub const UNIX_EPOCH: Self =
+ expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN);
+}
+
+impl From<NaiveDate> 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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::{NaiveDate, NaiveDateTime, Datelike, 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, or when the `NaiveDateTime` would be
+ /// out of range.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ self.date.with_year(year).map(|d| NaiveDateTime { date: d, ..*self })
+ }
+
+ /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed.
+ ///
+ /// See also the [`NaiveDate::with_month`] method.
+ ///
+ /// # Errors
+ ///
+ /// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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, or if the value for `month0` is
+ /// invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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, or if the value for `day` is invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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, or if the value for `day0` is invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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, or if the value for `ordinal` is
+ /// invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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, or if the value for `ordinal0` is
+ /// invalid.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, NaiveDateTime, Datelike};
+ ///
+ /// 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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<NaiveDateTime> {
+ 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::{TimeDelta, NaiveDate};
+///
+/// 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::seconds(1), hms(3, 5, 8));
+/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(-1), hms(3, 5, 6));
+/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(3600 + 60), hms(4, 6, 7));
+/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(86_400),
+/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap());
+/// assert_eq!(hms(3, 5, 7) + TimeDelta::days(365),
+/// 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::milliseconds(450), 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::milliseconds(-500), hmsm(3, 5, 59, 800));
+/// assert_eq!(leap + TimeDelta::milliseconds(500), hmsm(3, 5, 59, 1_800));
+/// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100));
+/// assert_eq!(leap + TimeDelta::seconds(10), hmsm(3, 6, 9, 300));
+/// assert_eq!(leap + TimeDelta::seconds(-10), hmsm(3, 5, 50, 300));
+/// assert_eq!(leap + TimeDelta::days(1),
+/// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap());
+/// ```
+///
+/// [leap second handling]: crate::NaiveTime#leap-second-handling
+impl Add<TimeDelta> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<Duration> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<TimeDelta> for NaiveDateTime {
+ #[inline]
+ 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<Duration> for NaiveDateTime {
+ #[inline]
+ 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<FixedOffset> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<Months> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ 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::{TimeDelta, NaiveDate};
+///
+/// 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::seconds(1), hms(3, 5, 6));
+/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(-1), hms(3, 5, 8));
+/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(3600 + 60), hms(2, 4, 7));
+/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(86_400),
+/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap());
+/// assert_eq!(hms(3, 5, 7) - TimeDelta::days(365),
+/// 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::milliseconds(670), 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::milliseconds(200), hmsm(3, 5, 59, 1_100));
+/// assert_eq!(leap - TimeDelta::milliseconds(500), hmsm(3, 5, 59, 800));
+/// assert_eq!(leap - TimeDelta::seconds(60), hmsm(3, 5, 0, 300));
+/// assert_eq!(leap - TimeDelta::days(1),
+/// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap());
+/// ```
+///
+/// [leap second handling]: crate::NaiveTime#leap-second-handling
+impl Sub<TimeDelta> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<Duration> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<TimeDelta> for NaiveDateTime {
+ #[inline]
+ 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<Duration> for NaiveDateTime {
+ #[inline]
+ 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<FixedOffset> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ #[inline]
+ 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<Months> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ 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::{TimeDelta, NaiveDate};
+///
+/// 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::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_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(),
+/// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500));
+/// ```
+///
+/// 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::seconds(3600) + TimeDelta::milliseconds(500));
+/// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap,
+/// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500));
+/// ```
+impl Sub<NaiveDateTime> 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<Days> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ 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<Days> for NaiveDateTime {
+ type Output = NaiveDateTime;
+
+ 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)
+ }
+}
+
+/// 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::<NaiveDateTime>(), 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::<NaiveDateTime>(), Ok(dt));
+///
+/// assert!("foo".parse::<NaiveDateTime>().is_err());
+/// ```
+impl str::FromStr for NaiveDateTime {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> ParseResult<NaiveDateTime> {
+ 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 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!(Some(default_date), NaiveDateTime::from_timestamp_opt(0, 0));
+/// ```
+impl Default for NaiveDateTime {
+ fn default() -> Self {
+ NaiveDateTime::from_timestamp_opt(0, 0).unwrap()
+ }
+}
+
+#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
+fn test_encodable_json<F, E>(to_string: F)
+where
+ F: Fn(&NaiveDateTime) -> Result<String, E>,
+ E: ::std::fmt::Debug,
+{
+ assert_eq!(
+ 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!(
+ 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!(
+ 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!(
+ 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!(
+ to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(),
+ Some(r#""-262143-01-01T00:00:00""#.into())
+ );
+ assert_eq!(
+ 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())
+ );
+}
+
+#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
+fn test_decodable_json<F, E>(from_str: F)
+where
+ F: Fn(&str) -> Result<NaiveDateTime, E>,
+ E: ::std::fmt::Debug,
+{
+ 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());
+}
+
+#[cfg(all(test, feature = "rustc-serialize"))]
+fn test_decodable_json_timestamp<F, E>(from_str: F)
+where
+ F: Fn(&str) -> Result<rustc_serialize::TsSeconds, E>,
+ E: ::std::fmt::Debug,
+{
+ assert_eq!(
+ *from_str("0").unwrap(),
+ NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(),
+ "should parse integers as timestamps"
+ );
+ assert_eq!(
+ *from_str("-1").unwrap(),
+ NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(),
+ "should parse integers as timestamps"
+ );
+}
diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs
new file mode 100644
index 0000000..d2b8a87
--- /dev/null
+++ b/src/naive/datetime/rustc_serialize.rs
@@ -0,0 +1,75 @@
+use super::NaiveDateTime;
+use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
+use std::ops::Deref;
+
+impl Encodable for NaiveDateTime {
+ fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
+ format!("{:?}", self).encode(s)
+ }
+}
+
+impl Decodable for NaiveDateTime {
+ fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
+ 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<TsSeconds> 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: Decoder>(d: &mut D) -> Result<TsSeconds, D::Error> {
+ Ok(TsSeconds(
+ NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0)
+ .ok_or_else(|| d.error("invalid timestamp"))?,
+ ))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::naive::datetime::test_encodable_json;
+ use crate::naive::datetime::{test_decodable_json, test_decodable_json_timestamp};
+ use rustc_serialize::json;
+
+ #[test]
+ fn test_encodable() {
+ test_encodable_json(json::encode);
+ }
+
+ #[test]
+ fn test_decodable() {
+ test_decodable_json(json::decode);
+ }
+
+ #[test]
+ fn test_decodable_timestamps() {
+ test_decodable_json_timestamp(json::decode);
+ }
+}
diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs
new file mode 100644
index 0000000..0a6cfd9
--- /dev/null
+++ b/src/naive/datetime/serde.rs
@@ -0,0 +1,1180 @@
+use core::fmt;
+use serde::{de, ser};
+
+use super::NaiveDateTime;
+use crate::offset::LocalResult;
+
+/// 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ value.parse().map_err(E::custom)
+ }
+}
+
+impl<'de> de::Deserialize<'de> for NaiveDateTime {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ 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 super::ne_timestamp;
+ use crate::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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
+ 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 `NaiveDateTime` from a nanoseconds timestamp
+ ///
+ /// Intended for use with `serde`s `deserialize_with` attribute.
+ ///
+ /// # Example:
+ ///
+ /// ```rust
+ /// # use chrono::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 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999).unwrap() });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(NanoSecondsTimestampVisitor)
+ }
+
+ pub(super) struct NanoSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor {
+ type Value = NaiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a unix timestamp")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_opt(
+ value.div_euclid(1_000_000_000),
+ (value.rem_euclid(1_000_000_000)) as u32,
+ )
+ .ok_or_else(|| E::custom(ne_timestamp(value)))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_opt(
+ (value / 1_000_000_000) as i64,
+ (value % 1_000_000_000) as u32,
+ )
+ .ok_or_else(|| E::custom(ne_timestamp(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<NaiveDateTime>
+/// }
+///
+/// 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<NaiveDateTime>
+ /// }
+ ///
+ /// 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<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
+ 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 `NaiveDateTime` from a nanosecond timestamp or none
+ ///
+ /// Intended for use with `serde`s `deserialize_with` attribute.
+ ///
+ /// # Example:
+ ///
+ /// ```rust
+ /// # use chrono::naive::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<NaiveDateTime>
+ /// }
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999) });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionNanoSecondsTimestampVisitor)
+ }
+
+ struct OptionNanoSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor {
+ type Value = Option<NaiveDateTime>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in nanoseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in nanoseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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 super::ne_timestamp;
+ use crate::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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ serializer.serialize_i64(dt.timestamp_micros())
+ }
+
+ /// Deserialize a `NaiveDateTime` from a microseconds timestamp
+ ///
+ /// Intended for use with `serde`s `deserialize_with` attribute.
+ ///
+ /// # Example:
+ ///
+ /// ```rust
+ /// # use chrono::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 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000).unwrap() });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MicroSecondsTimestampVisitor)
+ }
+
+ pub(super) struct MicroSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor {
+ type Value = NaiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a unix timestamp")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_micros(value)
+ .ok_or_else(|| E::custom(ne_timestamp(value)))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_opt(
+ (value / 1_000_000) as i64,
+ ((value % 1_000_000) * 1_000) as u32,
+ )
+ .ok_or_else(|| E::custom(ne_timestamp(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<NaiveDateTime>
+/// }
+///
+/// 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<NaiveDateTime>
+ /// }
+ ///
+ /// 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<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ match *opt {
+ Some(ref dt) => serializer.serialize_some(&dt.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::naive::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<NaiveDateTime>
+ /// }
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000) });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionMicroSecondsTimestampVisitor)
+ }
+
+ struct OptionMicroSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor {
+ type Value = Option<NaiveDateTime>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in microseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in microseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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 super::ne_timestamp;
+ use crate::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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ serializer.serialize_i64(dt.timestamp_millis())
+ }
+
+ /// Deserialize a `NaiveDateTime` from a milliseconds timestamp
+ ///
+ /// Intended for use with `serde`s `deserialize_with` attribute.
+ ///
+ /// # Example:
+ ///
+ /// ```rust
+ /// # use chrono::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 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000).unwrap() });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MilliSecondsTimestampVisitor)
+ }
+
+ pub(super) struct MilliSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor {
+ type Value = NaiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a unix timestamp")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_millis(value)
+ .ok_or_else(|| E::custom(ne_timestamp(value)))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ 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)))
+ }
+ }
+}
+
+/// 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<NaiveDateTime>
+/// }
+///
+/// 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<NaiveDateTime>
+ /// }
+ ///
+ /// 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<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ match *opt {
+ Some(ref dt) => serializer.serialize_some(&dt.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::naive::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<NaiveDateTime>
+ /// }
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) });
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000) });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionMilliSecondsTimestampVisitor)
+ }
+
+ struct OptionMilliSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor {
+ type Value = Option<NaiveDateTime>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in milliseconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ 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 super::ne_timestamp;
+ use crate::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<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ serializer.serialize_i64(dt.timestamp())
+ }
+
+ /// Deserialize a `NaiveDateTime` from a seconds timestamp
+ ///
+ /// Intended for use with `serde`s `deserialize_with` attribute.
+ ///
+ /// # Example:
+ ///
+ /// ```rust
+ /// # use chrono::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 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0).unwrap() });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(SecondsTimestampVisitor)
+ }
+
+ pub(super) struct SecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for SecondsTimestampVisitor {
+ type Value = NaiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a unix timestamp")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_opt(value, 0)
+ .ok_or_else(|| E::custom(ne_timestamp(value)))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ NaiveDateTime::from_timestamp_opt(value as i64, 0)
+ .ok_or_else(|| E::custom(ne_timestamp(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<NaiveDateTime>
+/// }
+///
+/// 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<NaiveDateTime>
+ /// }
+ ///
+ /// let my_s = S {
+ /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()),
+ /// };
+ /// let as_string = serde_json::to_string(&my_s)?;
+ /// assert_eq!(as_string, r#"{"time":1526522699}"#);
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ match *opt {
+ Some(ref dt) => serializer.serialize_some(&dt.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::naive::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<NaiveDateTime>
+ /// }
+ ///
+ /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?;
+ /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0) });
+ /// # Ok::<(), serde_json::Error>(())
+ /// ```
+ pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_option(OptionSecondsTimestampVisitor)
+ }
+
+ struct OptionSecondsTimestampVisitor;
+
+ impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor {
+ type Value = Option<NaiveDateTime>;
+
+ 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<D>(self, d: D) -> Result<Self::Value, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_i64(SecondsTimestampVisitor).map(Some)
+ }
+
+ /// Deserialize a timestamp in seconds since the epoch
+ fn visit_none<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+
+ /// Deserialize a timestamp in seconds since the epoch
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(None)
+ }
+ }
+}
+
+// lik? function to convert a LocalResult into a serde-ish Result
+pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
+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, max }))
+ }
+ LocalResult::Single(val) => Ok(val),
+ }
+}
+
+enum SerdeError<V: fmt::Display, D: fmt::Display> {
+ NonExistent { timestamp: V },
+ Ambiguous { timestamp: V, min: D, max: D },
+}
+
+/// Construct a [`SerdeError::NonExistent`]
+fn ne_timestamp<T: fmt::Display>(ts: T) -> SerdeError<T, u8> {
+ SerdeError::NonExistent::<T, u8> { timestamp: ts }
+}
+
+impl<V: fmt::Display, D: fmt::Display> fmt::Debug for SerdeError<V, D> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "ChronoSerdeError({})", self)
+ }
+}
+
+// impl<V: fmt::Display, D: fmt::Debug> core::error::Error for SerdeError<V, D> {}
+impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ SerdeError::NonExistent { timestamp } => {
+ write!(f, "value is not a legal timestamp: {}", timestamp)
+ }
+ SerdeError::Ambiguous { timestamp, min, max } => write!(
+ f,
+ "value is an ambiguous timestamp: {}, could be either of {}, {}",
+ timestamp, min, max
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::naive::datetime::{test_decodable_json, test_encodable_json};
+ 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() {
+ test_encodable_json(serde_json::to_string);
+ }
+
+ #[test]
+ fn test_serde_deserialize() {
+ test_decodable_json(|input| serde_json::from_str(input));
+ }
+
+ // 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<i64>,
+ #[serde(with = "ts_nanoseconds_option")]
+ two: Option<DateTime<Utc>>,
+ }
+
+ let expected =
+ Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) };
+ let bytes: Vec<u8> = serialize(&expected).unwrap();
+ let actual = deserialize::<Test>(&(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 0000000..4d36208
--- /dev/null
+++ b/src/naive/datetime/tests.rs
@@ -0,0 +1,593 @@
+use super::NaiveDateTime;
+use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, 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 naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis);
+ assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis());
+ #[cfg(feature = "alloc")]
+ assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted);
+ }
+
+ let invalid = [i64::MAX, i64::MIN];
+
+ for timestamp_millis in invalid.iter().copied() {
+ let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis);
+ assert!(naive_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!(
+ NaiveDateTime::from_timestamp_millis(secs * 1000),
+ NaiveDateTime::from_timestamp_opt(secs, 0)
+ );
+ }
+}
+
+#[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 naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros);
+ assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros());
+ #[cfg(feature = "alloc")]
+ assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted);
+ }
+
+ let invalid = [i64::MAX, i64::MIN];
+
+ for timestamp_micros in invalid.iter().copied() {
+ let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros);
+ assert!(naive_datetime.is_none());
+ }
+
+ // Test that the result of `from_timestamp_micros` 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().copied() {
+ assert_eq!(
+ NaiveDateTime::from_timestamp_micros(secs * 1_000_000),
+ NaiveDateTime::from_timestamp_opt(secs, 0)
+ );
+ }
+}
+
+#[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 naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos).unwrap();
+ assert_eq!(timestamp_nanos, naive_datetime.timestamp_nanos_opt().unwrap());
+ #[cfg(feature = "alloc")]
+ assert_eq!(naive_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.854775804";
+ let parsed: NaiveDateTime = maximum.parse().unwrap();
+ let nanos = parsed.timestamp_nanos_opt().unwrap();
+ assert_eq!(
+ NaiveDateTime::from_timestamp_nanos(nanos).unwrap(),
+ NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap()
+ );
+ // Minimum datetime in nanoseconds
+ let minimum = "1677-09-21T00:12:44.000000000";
+ let parsed: NaiveDateTime = minimum.parse().unwrap();
+ let nanos = parsed.timestamp_nanos_opt().unwrap();
+ assert_eq!(
+ NaiveDateTime::from_timestamp_nanos(nanos).unwrap(),
+ NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap()
+ );
+
+ // Test that the result of `from_timestamp_nanos` 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().copied() {
+ assert_eq!(
+ NaiveDateTime::from_timestamp_nanos(secs * 1_000_000_000),
+ NaiveDateTime::from_timestamp_opt(secs, 0)
+ );
+ }
+}
+
+#[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_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
+ 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: 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);
+ }
+
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10)));
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8)));
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86399), Some((2014, 5, 7, 7, 8, 8)));
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9)));
+ check((2014, 5, 6, 7, 8, 9), TimeDelta::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 + TimeDelta::seconds(86399),
+ Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)),
+ );
+ check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + TimeDelta::seconds(86_400), None);
+ check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), 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 - TimeDelta::seconds(1), None);
+ check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), 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::seconds(1)
+ );
+ assert_eq!(
+ since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
+ TimeDelta::seconds(-1)
+ );
+ assert_eq!(
+ since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
+ TimeDelta::seconds(86399)
+ );
+ assert_eq!(
+ since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)),
+ TimeDelta::seconds(999_999_999)
+ );
+}
+
+#[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::minutes(10_000_000);
+ assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10));
+ date += TimeDelta::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_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
+ let mut date = ymdhms(2016, 10, 1, 10, 10, 10);
+ date -= TimeDelta::minutes(10_000_000);
+ assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10));
+ date -= TimeDelta::days(10);
+ 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_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().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 = [
+ "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::<NaiveDateTime>() {
+ 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::<NaiveDateTime>() {
+ 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
+ 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::<NaiveDateTime>().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 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_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_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_opt().unwrap();
+ assert_eq!(
+ parsed,
+ NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap()
+ );
+
+ let minimum = "1677-09-21T00:12:44.000000000";
+ let parsed: NaiveDateTime = minimum.parse().unwrap();
+ let nanos = parsed.timestamp_nanos_opt().unwrap();
+ assert_eq!(
+ parsed,
+ NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap()
+ );
+
+ // Just beyond range
+ let maximum = "2262-04-11T23:47:16.854775804";
+ let parsed: NaiveDateTime = maximum.parse().unwrap();
+ let beyond_max = parsed + TimeDelta::milliseconds(300);
+ assert!(beyond_max.timestamp_nanos_opt().is_none());
+
+ // Far beyond range
+ let maximum = "2262-04-11T23:47:16.854775804";
+ let parsed: NaiveDateTime = maximum.parse().unwrap();
+ let beyond_max = parsed + TimeDelta::days(365);
+ assert!(beyond_max.timestamp_nanos_opt().is_none());
+}
+
+#[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_local_timezone(Utc).unwrap();
+ 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, LocalResult::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, LocalResult::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::<NaiveDateTime>(&bytes).unwrap(), dt_min);
+
+ let dt_max = NaiveDateTime::MAX;
+ let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap();
+ assert_eq!(rkyv::from_bytes::<NaiveDateTime>(&bytes).unwrap(), dt_max);
+}
diff --git a/src/naive/internals.rs b/src/naive/internals.rs
index 346063c..c6d7536 100644
--- a/src/naive/internals.rs
+++ b/src/naive/internals.rs
@@ -13,19 +13,23 @@
//! 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;
+use crate::Weekday;
+use core::fmt;
-/// The internal date representation. This also includes the packed `Mdf` value.
-pub type DateImpl = i32;
+/// The internal date representation: `year << 13 | Of`
+pub(super) type DateImpl = i32;
-pub const MAX_YEAR: DateImpl = i32::MAX >> 13;
-pub const MIN_YEAR: DateImpl = i32::MIN >> 13;
+/// 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: DateImpl = (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: DateImpl = (i32::MIN >> 13) + 1;
/// The year flags (aka the dominical letter).
///
@@ -35,25 +39,26 @@ pub const MIN_YEAR: DateImpl = i32::MIN >> 13;
/// 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] = [
+#[allow(unreachable_pub)] // public as an alias for benchmarks only
+#[derive(PartialEq, Eq, Copy, Clone, Hash)]
+pub struct YearFlags(pub(super) u8);
+
+pub(super) const A: YearFlags = YearFlags(0o15);
+pub(super) const AG: YearFlags = YearFlags(0o05);
+pub(super) const B: YearFlags = YearFlags(0o14);
+pub(super) const BA: YearFlags = YearFlags(0o04);
+pub(super) const C: YearFlags = YearFlags(0o13);
+pub(super) const CB: YearFlags = YearFlags(0o03);
+pub(super) const D: YearFlags = YearFlags(0o12);
+pub(super) const DC: YearFlags = YearFlags(0o02);
+pub(super) const E: YearFlags = YearFlags(0o11);
+pub(super) const ED: YearFlags = YearFlags(0o01);
+pub(super) const F: YearFlags = YearFlags(0o17);
+pub(super) const FE: YearFlags = YearFlags(0o07);
+pub(super) const G: YearFlags = YearFlags(0o16);
+pub(super) const GF: YearFlags = YearFlags(0o06);
+
+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,7 +77,7 @@ 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] = [
+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,
@@ -94,44 +99,48 @@ static YEAR_DELTAS: [u8; 401] = [
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]);
+pub(super) 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 - u32::from(YEAR_DELTAS[year_mod_400 as usize]);
+ ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32;
} 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
+pub(super) 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
}
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 +148,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)
}
@@ -170,13 +179,13 @@ impl fmt::Debug for YearFlags {
}
}
-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
+pub(super) const MIN_OL: u32 = 1 << 1;
+pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year
+pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
const XX: i8 = -128;
-static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [
+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 +228,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,
@@ -264,95 +273,106 @@ static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [
///
/// 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.
+///
+/// The methods implemented on `Of` always return a valid value.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
-pub struct Of(pub u32);
+pub(super) struct Of(u32);
impl Of {
#[inline]
- fn clamp_ordinal(ordinal: u32) -> u32 {
- if ordinal > 366 {
- 0
- } else {
- ordinal
- }
+ pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option<Of> {
+ let of = Of((ordinal << 4) | flags as u32);
+ of.validate()
}
- #[inline]
- pub fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of {
- let ordinal = Of::clamp_ordinal(ordinal);
- Of((ordinal << 4) | u32::from(flags))
+ pub(super) const fn from_date_impl(date_impl: DateImpl) -> Of {
+ // We assume the value in the `DateImpl` is valid.
+ Of((date_impl & 0b1_1111_1111_1111) as u32)
}
#[inline]
- pub fn from_mdf(Mdf(mdf): Mdf) -> Of {
+ pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option<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),
+ if mdl > MAX_MDL {
+ // Panicking on out-of-bounds indexing would be reasonable, but just return `None`.
+ return None;
}
+ // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value.
+ let v = MDL_TO_OL[mdl as usize];
+ let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3));
+ of.validate()
}
#[inline]
- pub fn valid(&self) -> bool {
- let Of(of) = *self;
- let ol = of >> 3;
- MIN_OL <= ol && ol <= MAX_OL
+ pub(super) const fn inner(&self) -> u32 {
+ self.0
}
+ /// Returns `(ordinal << 1) | leap-year-flag`.
#[inline]
- pub fn ordinal(&self) -> u32 {
- let Of(of) = *self;
- of >> 4
+ const fn ol(&self) -> u32 {
+ self.0 >> 3
}
#[inline]
- pub fn with_ordinal(&self, ordinal: u32) -> Of {
- let ordinal = Of::clamp_ordinal(ordinal);
- let Of(of) = *self;
- Of((of & 0b1111) | (ordinal << 4))
+ const fn validate(self) -> Option<Of> {
+ let ol = self.ol();
+ match ol >= MIN_OL && ol <= MAX_OL {
+ true => Some(self),
+ false => None,
+ }
}
#[inline]
- pub fn flags(&self) -> YearFlags {
- let Of(of) = *self;
- YearFlags((of & 0b1111) as u8)
+ pub(super) const fn ordinal(&self) -> u32 {
+ self.0 >> 4
}
#[inline]
- pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of {
- let Of(of) = *self;
- Of((of & !0b1111) | u32::from(flags))
+ pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
+ let of = Of((ordinal << 4) | (self.0 & 0b1111));
+ of.validate()
+ }
+
+ #[inline]
+ pub(super) const fn flags(&self) -> YearFlags {
+ YearFlags((self.0 & 0b1111) as u8)
}
#[inline]
- pub fn weekday(&self) -> Weekday {
+ pub(super) const fn weekday(&self) -> Weekday {
let Of(of) = *self;
- Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap()
+ weekday_from_u32_mod7((of >> 4) + (of & 0b111))
}
#[inline]
- pub fn isoweekdate_raw(&self) -> (u32, Weekday) {
+ pub(super) 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())
+ (weekord / 7, weekday_from_u32_mod7(weekord))
}
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
#[inline]
- pub fn to_mdf(&self) -> Mdf {
+ pub(super) const fn to_mdf(&self) -> Mdf {
Mdf::from_of(*self)
}
+ /// Returns an `Of` with the next day, or `None` if this is the last day of the year.
#[inline]
- pub fn succ(&self) -> Of {
- let Of(of) = *self;
- Of(of + (1 << 4))
+ pub(super) const fn succ(&self) -> Option<Of> {
+ let of = Of(self.0 + (1 << 4));
+ of.validate()
}
+ /// Returns an `Of` with the previous day, or `None` if this is the first day of the year.
#[inline]
- pub fn pred(&self) -> Of {
- let Of(of) = *self;
- Of(of - (1 << 4))
+ pub(super) const fn pred(&self) -> Option<Of> {
+ match self.ordinal() {
+ 1 => None,
+ _ => Some(Of(self.0 - (1 << 4))),
+ }
}
}
@@ -374,94 +394,88 @@ impl fmt::Debug for Of {
/// 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 implemented on `Mdf` do not always return a valid value.
+/// Dates that can't exist, like February 30, can still be represented.
+/// Use `Mdl::valid` to check whether the date is valid.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
-pub struct Mdf(pub u32);
+pub(super) struct Mdf(u32);
impl Mdf {
#[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<Mdf> {
+ match month >= 1 && month <= 12 && day >= 1 && day <= 31 {
+ true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
+ false => None,
}
}
#[inline]
- fn clamp_day(day: u32) -> u32 {
- if day > 31 {
- 0
- } else {
- day
- }
- }
-
- #[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))
- }
-
- #[inline]
- pub fn from_of(Of(of): Of) -> Mdf {
+ pub(super) const 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),
+ if ol <= MAX_OL {
+ // Array is indexed from `[1..=MAX_OL]`, with a `0` index having a meaningless value.
+ Mdf(of + ((OL_TO_MDL[ol as usize] as u32) << 3))
+ } else {
+ // Panicking here would be reasonable, but we are just going on with a safe value.
+ Mdf(0)
}
}
- #[inline]
- pub fn valid(&self) -> bool {
+ #[cfg(test)]
+ pub(super) const 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,
+ if mdl <= MAX_MDL {
+ // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value.
+ MDL_TO_OL[mdl as usize] >= 0
+ } else {
+ // Panicking here would be reasonable, but we are just going on with a safe value.
+ false
}
}
#[inline]
- pub fn month(&self) -> u32 {
+ pub(super) const fn month(&self) -> u32 {
let Mdf(mdf) = *self;
mdf >> 9
}
#[inline]
- pub fn with_month(&self, month: u32) -> Mdf {
- let month = Mdf::clamp_month(month);
+ pub(super) const fn with_month(&self, month: u32) -> Option<Mdf> {
+ if month > 12 {
+ return None;
+ }
+
let Mdf(mdf) = *self;
- Mdf((mdf & 0b1_1111_1111) | (month << 9))
+ Some(Mdf((mdf & 0b1_1111_1111) | (month << 9)))
}
#[inline]
- pub fn day(&self) -> u32 {
+ pub(super) const fn day(&self) -> u32 {
let Mdf(mdf) = *self;
(mdf >> 4) & 0b1_1111
}
#[inline]
- pub fn with_day(&self, day: u32) -> Mdf {
- let day = Mdf::clamp_day(day);
- let Mdf(mdf) = *self;
- Mdf((mdf & !0b1_1111_0000) | (day << 4))
- }
+ pub(super) const fn with_day(&self, day: u32) -> Option<Mdf> {
+ if day > 31 {
+ return None;
+ }
- #[inline]
- pub fn flags(&self) -> YearFlags {
let Mdf(mdf) = *self;
- YearFlags((mdf & 0b1111) as u8)
+ Some(Mdf((mdf & !0b1_1111_0000) | (day << 4)))
}
#[inline]
- pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
+ pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
let Mdf(mdf) = *self;
- Mdf((mdf & !0b1111) | u32::from(flags))
+ Mdf((mdf & !0b1111) | flags as u32)
}
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
#[inline]
- pub fn to_of(&self) -> Of {
+ pub(super) const fn to_of(&self) -> Option<Of> {
Of::from_mdf(*self)
}
}
@@ -480,16 +494,27 @@ impl fmt::Debug for Mdf {
}
}
+/// Create a `Weekday` from an `u32`, with Monday = 0.
+/// Infallible, takes any `n` and applies `% 7`.
+#[inline]
+const fn weekday_from_u32_mod7(n: u32) -> Weekday {
+ match n % 7 {
+ 0 => Weekday::Mon,
+ 1 => Weekday::Tue,
+ 2 => Weekday::Wed,
+ 3 => Weekday::Thu,
+ 4 => Weekday::Fri,
+ 5 => Weekday::Sat,
+ _ => Weekday::Sun,
+ }
+}
+
#[cfg(test)]
mod tests {
- #[cfg(test)]
- extern crate num_iter;
-
- use self::num_iter::range_inclusive;
+ use super::weekday_from_u32_mod7;
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 crate::Weekday;
const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G];
const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF];
@@ -533,10 +558,15 @@ mod tests {
#[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);
+ for ordinal in ordinal1..=ordinal2 {
+ let of = match Of::new(ordinal, flags) {
+ Some(of) => of,
+ None if !expected => continue,
+ None => panic!("Of::new({}, {:?}) returned None", ordinal, flags),
+ };
+
assert!(
- of.valid() == expected,
+ of.validate().is_some() == expected,
"ordinal {} = {:?} should be {} for dominical year {:?}",
ordinal,
of,
@@ -564,9 +594,14 @@ mod tests {
#[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({}, {}, {:?}) returned None", month, day, flags),
+ };
+
assert!(
mdf.valid() == expected,
"month {} day {} = {:?} should be {} for dominical year {:?}",
@@ -650,9 +685,8 @@ 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() {
+ for ordinal in 1u32..=366 {
+ if let Some(of) = Of::new(ordinal, flags) {
assert_eq!(of.ordinal(), ordinal);
}
}
@@ -662,12 +696,12 @@ mod tests {
#[test]
fn test_of_with_fields() {
fn check(flags: YearFlags, ordinal: u32) {
- let of = Of::new(ordinal, flags);
+ let of = Of::new(ordinal, flags).unwrap();
- for ordinal in range_inclusive(0u32, 1024) {
+ for ordinal in 0u32..=1024 {
let of = of.with_ordinal(ordinal);
- assert_eq!(of.valid(), Of::new(ordinal, flags).valid());
- if of.valid() {
+ assert_eq!(of, Of::new(ordinal, flags));
+ if let Some(of) = of {
assert_eq!(of.ordinal(), ordinal);
}
}
@@ -685,25 +719,25 @@ mod tests {
#[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);
+ assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun);
+ assert_eq!(Of::new(1, B).unwrap().weekday(), Weekday::Sat);
+ assert_eq!(Of::new(1, C).unwrap().weekday(), Weekday::Fri);
+ assert_eq!(Of::new(1, D).unwrap().weekday(), Weekday::Thu);
+ assert_eq!(Of::new(1, E).unwrap().weekday(), Weekday::Wed);
+ assert_eq!(Of::new(1, F).unwrap().weekday(), Weekday::Tue);
+ assert_eq!(Of::new(1, G).unwrap().weekday(), Weekday::Mon);
+ assert_eq!(Of::new(1, AG).unwrap().weekday(), Weekday::Sun);
+ assert_eq!(Of::new(1, BA).unwrap().weekday(), Weekday::Sat);
+ assert_eq!(Of::new(1, CB).unwrap().weekday(), Weekday::Fri);
+ assert_eq!(Of::new(1, DC).unwrap().weekday(), Weekday::Thu);
+ assert_eq!(Of::new(1, ED).unwrap().weekday(), Weekday::Wed);
+ assert_eq!(Of::new(1, FE).unwrap().weekday(), Weekday::Tue);
+ assert_eq!(Of::new(1, GF).unwrap().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 mut prev = Of::new(1, flags).unwrap().weekday();
+ for ordinal in 2u32..=flags.ndays() {
+ let of = Of::new(ordinal, flags).unwrap();
let expected = prev.succ();
assert_eq!(of.weekday(), expected);
prev = expected;
@@ -714,9 +748,13 @@ mod tests {
#[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);
@@ -729,20 +767,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);
@@ -772,44 +818,81 @@ mod tests {
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();
+ let (week, _) = Of::new(4 /* January 4 */, flags).unwrap().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());
+ for i in 0u32..=8192 {
+ if let Some(of) = Of(i).validate() {
+ assert!(of.to_mdf().valid());
+ }
}
}
#[test]
fn test_mdf_to_of() {
- for i in range_inclusive(0u32, 8192) {
+ for i in 0u32..=8192 {
let mdf = Mdf(i);
- assert_eq!(mdf.valid(), mdf.to_of().valid());
+ assert_eq!(mdf.valid(), mdf.to_of().is_some());
}
}
#[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());
+ for i in 0u32..=8192 {
+ if let Some(of) = Of(i).validate() {
+ assert_eq!(of, of.to_mdf().to_of().unwrap());
}
}
}
#[test]
fn test_mdf_to_of_to_mdf() {
- for i in range_inclusive(0u32, 8192) {
+ for i in 0u32..=8192 {
let mdf = Mdf(i);
if mdf.valid() {
- assert_eq!(mdf, mdf.to_of().to_mdf());
+ assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());
}
}
}
+
+ #[test]
+ fn test_invalid_returns_none() {
+ let regular_year = YearFlags::from_year(2023);
+ let leap_year = YearFlags::from_year(2024);
+ assert!(Of::new(0, regular_year).is_none());
+ assert!(Of::new(366, regular_year).is_none());
+ assert!(Of::new(366, leap_year).is_some());
+ assert!(Of::new(367, regular_year).is_none());
+
+ assert!(Mdf::new(0, 1, regular_year).is_none());
+ assert!(Mdf::new(13, 1, regular_year).is_none());
+ assert!(Mdf::new(1, 0, regular_year).is_none());
+ assert!(Mdf::new(1, 32, regular_year).is_none());
+ assert!(Mdf::new(2, 31, regular_year).is_some());
+
+ assert!(Of::from_mdf(Mdf::new(2, 30, regular_year).unwrap()).is_none());
+ assert!(Of::from_mdf(Mdf::new(2, 30, leap_year).unwrap()).is_none());
+ assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none());
+ assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some());
+ assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some());
+
+ assert!(Of::new(365, regular_year).unwrap().succ().is_none());
+ assert!(Of::new(365, leap_year).unwrap().succ().is_some());
+ assert!(Of::new(366, leap_year).unwrap().succ().is_none());
+
+ assert!(Of::new(1, regular_year).unwrap().pred().is_none());
+ assert!(Of::new(1, leap_year).unwrap().pred().is_none());
+ }
+
+ #[test]
+ fn test_weekday_from_u32_mod7() {
+ for i in 0..=1000 {
+ assert_eq!(weekday_from_u32_mod7(i), Weekday::try_from((i % 7) as u8).unwrap());
+ }
+ assert_eq!(weekday_from_u32_mod7(u32::MAX), Weekday::Thu);
+ }
}
diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs
index ece10f2..4b3d8d9 100644
--- a/src/naive/isoweek.rs
+++ b/src/naive/isoweek.rs
@@ -7,13 +7,23 @@ use core::fmt;
use super::internals::{DateImpl, Of, 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,
@@ -27,7 +37,7 @@ pub struct IsoWeek {
// 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 {
+pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek {
let (rawweek, _) = of.isoweekdate_raw();
let (year, week) = if rawweek < 1 {
// previous year
@@ -42,7 +52,8 @@ pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek {
(year, rawweek)
}
};
- IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) }
+ let flags = YearFlags::from_year(year);
+ IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) }
}
impl IsoWeek {
@@ -50,24 +61,24 @@ impl IsoWeek {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// 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.iso_week().year(), 2015);
- /// ~~~~
+ /// ```
///
/// This year number might not match the calendar year number.
/// Continuing the example...
///
- /// ~~~~
+ /// ```
/// # 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
}
@@ -77,14 +88,14 @@ impl IsoWeek {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::{NaiveDate, Datelike, 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
}
@@ -94,14 +105,14 @@ impl IsoWeek {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::{NaiveDate, Datelike, 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
}
}
@@ -112,26 +123,26 @@ impl IsoWeek {
///
/// # Example
///
-/// ~~~~
+/// ```
/// use chrono::{NaiveDate, Datelike};
///
-/// 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 {
+ if (0..=9999).contains(&year) {
write!(f, "{:04}-W{:02}", year, week)
} else {
// ISO 8601 requires the explicit sign for out-of-range years
@@ -142,22 +153,72 @@ impl fmt::Debug for IsoWeek {
#[cfg(test)]
mod tests {
- use naive::{internals, MAX_DATE, MIN_DATE};
- use Datelike;
+ #[cfg(feature = "rkyv-validation")]
+ use super::IsoWeek;
+ use crate::naive::{internals, NaiveDate};
+ use crate::Datelike;
#[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.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.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::<IsoWeek>(&bytes).unwrap(), minweek);
+
+ let maxweek = NaiveDate::MAX.iso_week();
+ let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap();
+ assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), maxweek);
}
}
diff --git a/src/naive/mod.rs b/src/naive/mod.rs
new file mode 100644
index 0000000..c8c281b
--- /dev/null
+++ b/src/naive/mod.rs
@@ -0,0 +1,39 @@
+//! 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.
+
+pub(crate) mod date;
+pub(crate) mod datetime;
+mod internals;
+pub(crate) mod isoweek;
+pub(crate) mod time;
+
+pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator, NaiveWeek};
+#[allow(deprecated)]
+pub use self::date::{MAX_DATE, MIN_DATE};
+#[cfg(feature = "rustc-serialize")]
+#[allow(deprecated)]
+pub use self::datetime::rustc_serialize::TsSeconds;
+#[allow(deprecated)]
+pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME};
+pub use self::isoweek::IsoWeek;
+pub use self::time::NaiveTime;
+
+#[cfg(feature = "__internal_bench")]
+#[doc(hidden)]
+pub use self::internals::YearFlags as __BenchYearFlags;
+
+/// 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::*;
+}
diff --git a/src/naive/time.rs b/src/naive/time.rs
deleted file mode 100644
index 1ddc9fb..0000000
--- a/src/naive/time.rs
+++ /dev/null
@@ -1,1814 +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> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<I>
- where
- I: Iterator<Item = B> + Clone,
- B: Borrow<Item<'a>>,
- {
- 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<StrftimeItems<'a>> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<NaiveTime> {
- 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<H: hash::Hasher>(&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<OldDuration> for NaiveTime {
- type Output = NaiveTime;
-
- #[inline]
- fn add(self, rhs: OldDuration) -> NaiveTime {
- self.overflowing_add_signed(rhs).0
- }
-}
-
-impl AddAssign<OldDuration> 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<OldDuration> for NaiveTime {
- type Output = NaiveTime;
-
- #[inline]
- fn sub(self, rhs: OldDuration) -> NaiveTime {
- self.overflowing_sub_signed(rhs).0
- }
-}
-
-impl SubAssign<OldDuration> 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<NaiveTime> 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::<NaiveTime>(), Ok(t));
-///
-/// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678);
-/// assert_eq!("23:56:4.012345678".parse::<NaiveTime>(), Ok(t));
-///
-/// let t = NaiveTime::from_hms_nano(23, 59, 59, 1_234_567_890); // leap second
-/// assert_eq!("23:59:60.23456789".parse::<NaiveTime>(), Ok(t));
-///
-/// assert!("foo".parse::<NaiveTime>().is_err());
-/// ~~~~
-impl str::FromStr for NaiveTime {
- type Err = ParseError;
-
- fn from_str(s: &str) -> ParseResult<NaiveTime> {
- 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()
- }
-}
-
-#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
-fn test_encodable_json<F, E>(to_string: F)
-where
- F: Fn(&NaiveTime) -> Result<String, E>,
- 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<F, E>(from_str: F)
-where
- F: Fn(&str) -> Result<NaiveTime, E>,
- 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<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
- format!("{:?}", self).encode(s)
- }
- }
-
- impl Decodable for NaiveTime {
- fn decode<D: Decoder>(d: &mut D) -> Result<NaiveTime, D::Error> {
- 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- 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 {
- write!(formatter, "a formatted time string")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<NaiveTime, E>
- where
- E: de::Error,
- {
- value.parse().map_err(E::custom)
- }
- }
-
- impl<'de> de::Deserialize<'de> for NaiveTime {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- 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 = |h, m, s, mi| NaiveTime::from_hms_milli(h, m, s, mi);
-
- 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 = |h, m, s, mi| NaiveTime::from_hms_milli(h, m, s, mi);
-
- 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::<NaiveTime>() {
- 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::<NaiveTime>() {
- 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::<NaiveTime>().is_err());
- assert!("x".parse::<NaiveTime>().is_err());
- assert!("15".parse::<NaiveTime>().is_err());
- assert!("15:8".parse::<NaiveTime>().is_err());
- assert!("15:8:x".parse::<NaiveTime>().is_err());
- assert!("15:8:9x".parse::<NaiveTime>().is_err());
- assert!("23:59:61".parse::<NaiveTime>().is_err());
- assert!("12:34:56.x".parse::<NaiveTime>().is_err());
- assert!("12:34:56. 0".parse::<NaiveTime>().is_err());
- }
-
- #[test]
- fn test_time_parse_from_str() {
- let hms = |h, m, s| NaiveTime::from_hms(h, m, s);
- 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 0000000..0bdb765
--- /dev/null
+++ b/src/naive/time/mod.rs
@@ -0,0 +1,1612 @@
+// 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::{
+ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult,
+ Parsed, StrftimeItems,
+};
+use crate::{expect, try_opt};
+use crate::{FixedOffset, TimeDelta, Timelike};
+
+#[cfg(feature = "rustc-serialize")]
+mod rustc_serialize;
+
+#[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, Utc};
+///
+/// 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_local_timezone(Utc).unwrap();
+/// # 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 + 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::{Utc, NaiveDate};
+///
+/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap().and_local_timezone(Utc).unwrap();
+/// 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<NaiveTime> {
+ 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> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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::{TimeDelta, NaiveTime};
+ ///
+ /// 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::hours(11)),
+ /// (from_hms(14, 4, 5), 0));
+ /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(23)),
+ /// (from_hms(2, 4, 5), 86_400));
+ /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)),
+ /// (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::{TimeDelta, NaiveTime};
+ ///
+ /// 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::hours(2)),
+ /// (from_hms(1, 4, 5), 0));
+ /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(17)),
+ /// (from_hms(10, 4, 5), 86_400));
+ /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(-22)),
+ /// (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::{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, 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::milliseconds(25));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)),
+ /// TimeDelta::milliseconds(975));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)),
+ /// TimeDelta::seconds(7));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)),
+ /// TimeDelta::seconds(5 * 60));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)),
+ /// TimeDelta::seconds(3 * 3600));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)),
+ /// TimeDelta::seconds(-3600));
+ /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)),
+ /// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100));
+ /// ```
+ ///
+ /// 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::seconds(1));
+ /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)),
+ /// TimeDelta::milliseconds(1500));
+ /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)),
+ /// TimeDelta::seconds(60));
+ /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)),
+ /// TimeDelta::seconds(1));
+ /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)),
+ /// TimeDelta::seconds(61));
+ /// ```
+ #[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::NaiveTime;
+ /// use chrono::format::strftime::StrftimeItems;
+ ///
+ /// 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<I>
+ where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+ {
+ 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<StrftimeItems<'a>> {
+ 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_attr(not(feature = "std"), doc = "```ignore")]
+ #[cfg_attr(feature = "std", doc = "```")]
+ /// # 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_attr(not(feature = "std"), doc = "```ignore")]
+ #[cfg_attr(feature = "std", doc = "```")]
+ /// # 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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<NaiveTime> {
+ 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::{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::zero(), from_hmsm(3, 5, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(1), from_hmsm(3, 5, 8, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-1), from_hmsm(3, 5, 6, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(60 + 4), from_hmsm(3, 6, 11, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80));
+/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(280), from_hmsm(3, 5, 8, 230));
+/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(-980), 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::seconds(22*60*60), from_hmsm(1, 5, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-8*60*60), from_hmsm(19, 5, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::days(800), 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::milliseconds(-500), from_hmsm(3, 5, 59, 800));
+/// assert_eq!(leap + TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 1_800));
+/// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100));
+/// assert_eq!(leap + TimeDelta::seconds(10), from_hmsm(3, 6, 9, 300));
+/// assert_eq!(leap + TimeDelta::seconds(-10), from_hmsm(3, 5, 50, 300));
+/// assert_eq!(leap + TimeDelta::days(1), from_hmsm(3, 5, 59, 300));
+/// ```
+///
+/// [leap second handling]: crate::NaiveTime#leap-second-handling
+impl Add<TimeDelta> 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<TimeDelta> 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<Duration> 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<Duration> 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<FixedOffset> 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::{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::zero(), from_hmsm(3, 5, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(1), from_hmsm(3, 5, 6, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(60 + 5), from_hmsm(3, 4, 2, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920));
+/// assert_eq!(from_hmsm(3, 5, 7, 950) - TimeDelta::milliseconds(280), 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::seconds(8*60*60), from_hmsm(19, 5, 7, 0));
+/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::days(800), 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::milliseconds(200), from_hmsm(3, 5, 59, 1_100));
+/// assert_eq!(leap - TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 800));
+/// assert_eq!(leap - TimeDelta::seconds(60), from_hmsm(3, 5, 0, 300));
+/// assert_eq!(leap - TimeDelta::days(1), from_hmsm(3, 6, 0, 300));
+/// ```
+///
+/// [leap second handling]: crate::NaiveTime#leap-second-handling
+impl Sub<TimeDelta> 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<TimeDelta> 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<Duration> 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<Duration> 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<FixedOffset> 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::{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, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero());
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), TimeDelta::milliseconds(25));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), TimeDelta::milliseconds(975));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), TimeDelta::seconds(7));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), TimeDelta::seconds(5 * 60));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), TimeDelta::seconds(3 * 3600));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), TimeDelta::seconds(-3600));
+/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800),
+/// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100));
+/// ```
+///
+/// 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::seconds(1));
+/// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0),
+/// TimeDelta::milliseconds(1500));
+/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::seconds(60));
+/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::seconds(1));
+/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000),
+/// TimeDelta::seconds(61));
+/// ```
+impl Sub<NaiveTime> 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, ".{: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::<NaiveTime>(), Ok(t));
+///
+/// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap();
+/// assert_eq!("23:56:4.012345678".parse::<NaiveTime>(), 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::<NaiveTime>(), Ok(t));
+///
+/// // Seconds are optional
+/// let t = NaiveTime::from_hms_opt(23, 56, 0).unwrap();
+/// assert_eq!("23:56".parse::<NaiveTime>(), Ok(t));
+///
+/// assert!("foo".parse::<NaiveTime>().is_err());
+/// ```
+impl str::FromStr for NaiveTime {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> ParseResult<NaiveTime> {
+ 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()
+ }
+}
+
+#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
+fn test_encodable_json<F, E>(to_string: F)
+where
+ F: Fn(&NaiveTime) -> Result<String, E>,
+ E: ::std::fmt::Debug,
+{
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(),
+ Some(r#""00:00:00""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(),
+ Some(r#""00:00:00.950""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(),
+ Some(r#""00:00:60""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(),
+ Some(r#""00:01:02""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(),
+ Some(r#""03:05:07.098765432""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(),
+ Some(r#""07:08:09""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(),
+ Some(r#""12:34:56.000789""#.into())
+ );
+ assert_eq!(
+ to_string(&NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(),
+ Some(r#""23:59:60.999999999""#.into())
+ );
+}
+
+#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
+fn test_decodable_json<F, E>(from_str: F)
+where
+ F: Fn(&str) -> Result<NaiveTime, E>,
+ E: ::std::fmt::Debug,
+{
+ 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());
+ // 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());
+}
diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs
new file mode 100644
index 0000000..3c5c47a
--- /dev/null
+++ b/src/naive/time/rustc_serialize.rs
@@ -0,0 +1,30 @@
+use super::NaiveTime;
+use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
+
+impl Encodable for NaiveTime {
+ fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
+ format!("{:?}", self).encode(s)
+ }
+}
+
+impl Decodable for NaiveTime {
+ fn decode<D: Decoder>(d: &mut D) -> Result<NaiveTime, D::Error> {
+ d.read_str()?.parse().map_err(|_| d.error("invalid time"))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::naive::time::{test_decodable_json, test_encodable_json};
+ use rustc_serialize::json;
+
+ #[test]
+ fn test_encodable() {
+ test_encodable_json(json::encode);
+ }
+
+ #[test]
+ fn test_decodable() {
+ test_decodable_json(json::decode);
+ }
+}
diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs
new file mode 100644
index 0000000..0992fb5
--- /dev/null
+++ b/src/naive/time/serde.rs
@@ -0,0 +1,69 @@
+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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ value.parse().map_err(E::custom)
+ }
+}
+
+impl<'de> de::Deserialize<'de> for NaiveTime {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ deserializer.deserialize_str(NaiveTimeVisitor)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::naive::time::{test_decodable_json, test_encodable_json};
+ use crate::NaiveTime;
+
+ #[test]
+ fn test_serde_serialize() {
+ test_encodable_json(serde_json::to_string);
+ }
+
+ #[test]
+ fn test_serde_deserialize() {
+ test_decodable_json(|input| 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 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 0000000..a991117
--- /dev/null
+++ b/src/naive/time/tests.rs
@@ -0,0 +1,389 @@
+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::milliseconds(100), hmsm(3, 6, 0, 0));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-1800), hmsm(3, 5, 58, 500));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-800), hmsm(3, 5, 59, 500));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-100), hmsm(3, 5, 59, 1_200));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(100), hmsm(3, 5, 59, 1_400));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(1800), hmsm(3, 6, 1, 100));
+ check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap
+ check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(-86399), hmsm(3, 6, 0, 900));
+ check!(hmsm(3, 5, 59, 900), TimeDelta::days(12345), hmsm(3, 5, 59, 900));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(1), hmsm(3, 5, 59, 300));
+ check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(-1), hmsm(3, 6, 0, 300));
+
+ // regression tests for #37
+ check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-990), hmsm(23, 59, 59, 10));
+ check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-9990), 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::hours(11)),
+ (hmsm(14, 4, 5, 678), 0)
+ );
+ assert_eq!(
+ hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(23)),
+ (hmsm(2, 4, 5, 678), 86_400)
+ );
+ assert_eq!(
+ hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(-7)),
+ (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::days(1)),
+ (hmsm(3, 4, 59, 678), 86_400)
+ );
+ assert_eq!(
+ hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::days(-1)),
+ (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::hours(10);
+ assert_eq!(time, hms(22, 12, 12));
+ time += TimeDelta::hours(10);
+ 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::hours(10);
+ assert_eq!(time, hms(2, 12, 12));
+ time -= TimeDelta::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 = |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::milliseconds(300));
+ check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::seconds(3600 + 60 + 1));
+ check!(
+ hmsm(3, 5, 7, 200),
+ hmsm(2, 4, 6, 300),
+ TimeDelta::seconds(3600 + 60) + TimeDelta::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, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::milliseconds(400));
+ //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::milliseconds(1400));
+ //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::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) + TimeDelta::milliseconds(400), hmsm(3, 5, 7, 200));
+ //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::milliseconds(400), 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::<NaiveTime>() {
+ 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::<NaiveTime>() {
+ 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
+ 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::<NaiveTime>().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::<NaiveTime>(&bytes).unwrap(), t_min);
+
+ let t_max = NaiveTime::MAX;
+ let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap();
+ assert_eq!(rkyv::from_bytes::<NaiveTime>(&bytes).unwrap(), t_max);
+}
diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs
index 83f42a1..8f37558 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;
+
+#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
+use rkyv::{Archive, Deserialize, Serialize};
use super::{LocalResult, Offset, TimeZone};
-use div::div_mod_floor;
-use naive::{NaiveDate, NaiveDateTime, NaiveTime};
-use DateTime;
-use Timelike;
+use crate::format::{scan, ParseError, OUT_OF_RANGE};
+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<FixedOffset>` instances. See the [`east`](#method.east) and
-/// [`west`](#method.west) methods for examples.
+/// `DateTime<FixedOffset>` 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,21 @@ impl FixedOffset {
/// The negative `secs` means the Western Hemisphere.
///
/// Returns `None` on the out-of-bound `secs`.
- pub fn east_opt(secs: i32) -> Option<FixedOffset> {
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(not(feature = "std"), doc = "```ignore")]
+ #[cfg_attr(feature = "std", doc = "```")]
+ /// 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<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: secs })
} else {
@@ -59,16 +72,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 +82,21 @@ impl FixedOffset {
/// The negative `secs` means the Eastern Hemisphere.
///
/// Returns `None` on the out-of-bound `secs`.
- pub fn west_opt(secs: i32) -> Option<FixedOffset> {
+ ///
+ /// # Example
+ ///
+ #[cfg_attr(not(feature = "std"), doc = "```ignore")]
+ #[cfg_attr(feature = "std", doc = "```")]
+ /// 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<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: -secs })
} else {
@@ -87,17 +106,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<Self, Self::Err> {
+ 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;
@@ -130,8 +158,10 @@ 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)
} else {
@@ -146,99 +176,63 @@ 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<T>(lhs: &T, rhs: i32) -> T
-where
- T: Timelike + Add<OldDuration, Output = T>,
-{
- // 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<FixedOffset> for NaiveTime {
- type Output = NaiveTime;
-
- #[inline]
- fn add(self, rhs: FixedOffset) -> NaiveTime {
- add_with_leapsecond(&self, rhs.local_minus_utc)
- }
-}
-
-impl Sub<FixedOffset> for NaiveTime {
- type Output = NaiveTime;
-
- #[inline]
- fn sub(self, rhs: FixedOffset) -> NaiveTime {
- add_with_leapsecond(&self, -rhs.local_minus_utc)
- }
-}
-
-impl Add<FixedOffset> for NaiveDateTime {
- type Output = NaiveDateTime;
-
- #[inline]
- fn add(self, rhs: FixedOffset) -> NaiveDateTime {
- add_with_leapsecond(&self, rhs.local_minus_utc)
- }
-}
-
-impl Sub<FixedOffset> for NaiveDateTime {
- type Output = NaiveDateTime;
-
- #[inline]
- fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
- add_with_leapsecond(&self, -rhs.local_minus_utc)
- }
-}
-
-impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
- type Output = DateTime<Tz>;
-
- #[inline]
- fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
- add_with_leapsecond(&self, rhs.local_minus_utc)
- }
-}
-
-impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
- type Output = DateTime<Tz>;
-
- #[inline]
- fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
- 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<FixedOffset> {
+ 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::<FixedOffset>(&bytes).unwrap(), offset);
+ }
}
diff --git a/src/offset/local.rs b/src/offset/local.rs
deleted file mode 100644
index 1abb3a9..0000000
--- 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<Local> {
- 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<Local>`
-/// instances.
-///
-/// # Example
-///
-/// ~~~~
-/// use chrono::{Local, DateTime, TimeZone};
-///
-/// let dt: DateTime<Local> = Local::now();
-/// let dt: DateTime<Local> = 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> {
- Local::now().date()
- }
-
- /// Returns a `DateTime` which corresponds to the current date.
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
- pub fn now() -> DateTime<Local> {
- tm_to_datetime(Timespec::now().local())
- }
-
- /// Returns a `DateTime` which corresponds to the current date.
- #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
- pub fn now() -> DateTime<Local> {
- use super::Utc;
- let now: DateTime<Utc> = 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<FixedOffset> {
- self.from_local_date(local).map(|date| *date.offset())
- }
-
- fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
- 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<Date<Local>> {
- // 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<DateTime<Local>> {
- 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<DateTime<Local>> {
- 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<Local> {
- 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<Local> {
- // 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<Local> {
- 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 0000000..37e7889
--- /dev/null
+++ b/src/offset/local/mod.rs
@@ -0,0 +1,537 @@
+// 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::{LocalResult, TimeZone};
+use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
+#[allow(deprecated)]
+use crate::Date;
+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(
+ not(unix),
+ not(windows),
+ not(all(
+ target_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+ ))
+))]
+mod inner {
+ use crate::{FixedOffset, LocalResult, NaiveDateTime};
+
+ pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ LocalResult::Single(FixedOffset::east_opt(0).unwrap())
+ }
+
+ pub(super) fn offset_from_local_datetime(
+ _local_time: &NaiveDateTime,
+ ) -> LocalResult<FixedOffset> {
+ LocalResult::Single(FixedOffset::east_opt(0).unwrap())
+ }
+}
+
+#[cfg(all(
+ target_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+))]
+mod inner {
+ use crate::{Datelike, FixedOffset, LocalResult, NaiveDateTime, Timelike};
+
+ pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset();
+ LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
+ }
+
+ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ 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.
+ LocalResult::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<Local>`
+/// instances.
+///
+/// # Example
+///
+/// ```
+/// use chrono::{Local, DateTime, TimeZone};
+///
+/// let dt1: DateTime<Local> = Local::now();
+/// let dt2: DateTime<Local> = 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))]
+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> {
+ Local::now().date()
+ }
+
+ /// Returns a `DateTime<Local>` which corresponds to the current date, time and offset from
+ /// UTC.
+ ///
+ /// See also the similar [`Utc::now()`] which returns `DateTime<Utc>`, 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<FixedOffset>`
+ /// let now_fixed_offset = Local::now().fixed_offset();
+ /// // or
+ /// let now_fixed_offset: DateTime<FixedOffset> = 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<Local> {
+ 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) -> LocalResult<FixedOffset> {
+ // Get the offset at local midnight.
+ self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN))
+ }
+
+ fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ 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<Ordering> {
+ Some(self.transition_utc.cmp(&other.transition_utc))
+ }
+}
+
+#[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,
+) -> LocalResult<FixedOffset> {
+ 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 LocalResult::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 => LocalResult::Single(t.offset_before),
+ Ordering::Less => LocalResult::Ambiguous(t.offset_before, t.offset_after),
+ Ordering::Greater => {
+ if dt == wall_earliest {
+ LocalResult::Single(t.offset_before)
+ } else if dt == wall_latest {
+ LocalResult::Single(t.offset_after)
+ } else {
+ LocalResult::None
+ }
+ }
+ };
+ }
+ }
+ LocalResult::Single(transitions.last().unwrap().offset_after)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Local;
+ #[cfg(windows)]
+ use crate::offset::local::{lookup_with_dst_transitions, Transition};
+ use crate::offset::TimeZone;
+ use crate::{Datelike, TimeDelta, Utc};
+ #[cfg(windows)]
+ use crate::{FixedOffset, LocalResult, 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() - TimeDelta::days(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() + TimeDelta::days(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: LocalResult<FixedOffset>,
+ ) {
+ 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, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None);
+ compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst));
+
+ compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, LocalResult::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, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Ambiguous(dst, std));
+ compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, LocalResult::Single(std));
+
+ compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::None);
+ compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, LocalResult::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, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst));
+
+ compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, LocalResult::None);
+ compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::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, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, LocalResult::None);
+ compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Single(std));
+
+ compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::Ambiguous(std, dst));
+ compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::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, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::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, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std));
+ compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None);
+ compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst));
+ compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::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()),
+ LocalResult::Single(std)
+ );
+ assert_eq!(
+ lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()),
+ LocalResult::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),
+ LocalResult::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()),
+ LocalResult::Single(dst)
+ );
+ assert_eq!(
+ lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()),
+ LocalResult::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),
+ LocalResult::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::<Local>(&bytes).unwrap(), super::ArchivedLocal);
+ }
+}
diff --git a/src/offset/local/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs
new file mode 100644
index 0000000..780e15a
--- /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<io::Error> for Error {
+ fn from(error: io::Error) -> Self {
+ Error::Io(error)
+ }
+}
+
+impl From<ParseIntError> for Error {
+ fn from(error: ParseIntError) -> Self {
+ Error::ParseInt(error)
+ }
+}
+
+impl From<SystemTimeError> for Error {
+ fn from(error: SystemTimeError) -> Self {
+ Error::SystemTime(error)
+ }
+}
+
+impl From<Utf8Error> 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 0000000..47cc037
--- /dev/null
+++ b/src/offset/local/tz_info/parser.rs
@@ -0,0 +1,333 @@
+use std::io::{self, ErrorKind};
+use std::iter;
+use std::num::ParseIntError;
+use std::str::{self, FromStr};
+
+use super::rule::TransitionRule;
+use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition};
+use super::Error;
+
+pub(super) fn parse(bytes: &[u8]) -> Result<TimeZone, Error> {
+ 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<Self, Error> {
+ 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<i64, Error> {
+ 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<Self, Error> {
+ 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<u32, Error> {
+ let mut buf = [0; 4];
+ buf.copy_from_slice(self.read_exact(4)?);
+ Ok(u32::from_be_bytes(buf))
+ }
+
+ /// 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<bool, io::Error> {
+ 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<F: Fn(&u8) -> 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<T: FromStr<Err = ParseIntError>>(&mut self) -> Result<T, Error> {
+ 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<F: Fn(&u8) -> 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<i32, Error> {
+ 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<i64, Error> {
+ 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 0000000..369e317
--- /dev/null
+++ b/src/offset/local/tz_info/rule.rs
@@ -0,0 +1,1045 @@
+use std::cmp::Ordering;
+
+use super::parser::Cursor;
+use super::timezone::{LocalTimeType, SECONDS_PER_WEEK};
+use super::{
+ Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR,
+ SECONDS_PER_DAY,
+};
+
+/// 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<Self, Error> {
+ 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: i64,
+ year: i32,
+ ) -> Result<crate::LocalResult<LocalTimeType>, Error> {
+ match self {
+ TransitionRule::Fixed(local_time_type) => {
+ Ok(crate::LocalResult::Single(*local_time_type))
+ }
+ TransitionRule::Alternate(alternate_time) => {
+ alternate_time.find_local_time_type_from_local(local_time, year)
+ }
+ }
+ }
+}
+
+impl From<LocalTimeType> for TransitionRule {
+ fn from(inner: LocalTimeType) -> Self {
+ TransitionRule::Fixed(inner)
+ }
+}
+
+impl From<AlternateTime> 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<Self, Error> {
+ // 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 = match UtcDateTime::from_timespec(unix_time) {
+ Ok(dt) => dt.year,
+ Err(error) => return Err(error),
+ };
+
+ // Check if the current year is valid for the following computations
+ if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) {
+ 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(&current_year_dst_start_unix_time, &current_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: i64,
+ current_year: i32,
+ ) -> Result<crate::LocalResult<LocalTimeType>, Error> {
+ // Check if the current year is valid for the following computations
+ if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) {
+ return Err(Error::OutOfRange("out of range date time"));
+ }
+
+ 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::LocalResult::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::LocalResult::Single(self.std))
+ } else if local_time > dst_start_transition_start
+ && local_time < dst_start_transition_end
+ {
+ Ok(crate::LocalResult::None)
+ } else if local_time >= dst_start_transition_end
+ && local_time < dst_end_transition_end
+ {
+ Ok(crate::LocalResult::Single(self.dst))
+ } else if local_time >= dst_end_transition_end
+ && local_time <= dst_end_transition_start
+ {
+ Ok(crate::LocalResult::Ambiguous(self.std, self.dst))
+ } else {
+ Ok(crate::LocalResult::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::LocalResult::Single(self.dst))
+ } else if local_time >= dst_end_transition_end
+ && local_time <= dst_end_transition_start
+ {
+ Ok(crate::LocalResult::Ambiguous(self.std, self.dst))
+ } else if local_time > dst_end_transition_end
+ && local_time < dst_start_transition_start
+ {
+ Ok(crate::LocalResult::Single(self.std))
+ } else if local_time >= dst_start_transition_start
+ && local_time < dst_start_transition_end
+ {
+ Ok(crate::LocalResult::None)
+ } else {
+ Ok(crate::LocalResult::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::LocalResult::Single(self.std))
+ } else if local_time >= dst_start_transition_end
+ && local_time <= dst_start_transition_start
+ {
+ Ok(crate::LocalResult::Ambiguous(self.dst, self.std))
+ } else if local_time > dst_start_transition_start
+ && local_time < dst_end_transition_start
+ {
+ Ok(crate::LocalResult::Single(self.dst))
+ } else if local_time >= dst_end_transition_start
+ && local_time < dst_end_transition_end
+ {
+ Ok(crate::LocalResult::None)
+ } else {
+ Ok(crate::LocalResult::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::LocalResult::Single(self.dst))
+ } else if local_time > dst_end_transition_start
+ && local_time < dst_end_transition_end
+ {
+ Ok(crate::LocalResult::None)
+ } else if local_time >= dst_end_transition_end
+ && local_time < dst_start_transition_end
+ {
+ Ok(crate::LocalResult::Single(self.std))
+ } else if local_time >= dst_start_transition_end
+ && local_time <= dst_start_transition_start
+ {
+ Ok(crate::LocalResult::Ambiguous(self.dst, self.std))
+ } else {
+ Ok(crate::LocalResult::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<i32, Error> {
+ let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?;
+
+ if !(0..=24).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<i32, Error> {
+ 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<i32, Error> {
+ 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<Self, Error> {
+ 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<Self, Error> {
+ 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<Self, Error> {
+ 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_occurence_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_occurence_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<Self, Error> {
+ 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_value() as i64 && year <= i32::max_value() 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_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 0000000..02b6f34
--- /dev/null
+++ b/src/offset/local/tz_info/timezone.rs
@@ -0,0 +1,950 @@
+//! 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::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
+
+/// Time zone
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub(crate) struct TimeZone {
+ /// List of transitions
+ transitions: Vec<Transition>,
+ /// List of local time types (cannot be empty)
+ local_time_types: Vec<LocalTimeType>,
+ /// List of leap seconds
+ leap_seconds: Vec<LeapSecond>,
+ /// Extra transition rule applicable after the last transition
+ extra_rule: Option<TransitionRule>,
+}
+
+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<Self, Error> {
+ 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<Self, Error> {
+ if tz_string.is_empty() {
+ return Err(Error::InvalidTzString("empty TZ string"));
+ }
+
+ 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(target_os = "android")]
+ {
+ if let Ok(bytes) = android_tzdata::find_tz_data(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<Transition>,
+ local_time_types: Vec<LocalTimeType>,
+ leap_seconds: Vec<LeapSecond>,
+ extra_rule: Option<TransitionRule>,
+ ) -> Result<Self, Error> {
+ 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<Self, Error> {
+ 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<Self, Error> {
+ parser::parse(bytes)
+ }
+
+ /// Construct a time zone with the specified UTC offset in seconds
+ fn fixed(ut_offset: i32) -> Result<Self, Error> {
+ 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)
+ }
+
+ // should we pass NaiveDateTime all the way through to this fn?
+ pub(crate) fn find_local_time_type_from_local(
+ &self,
+ local_time: i64,
+ year: i32,
+ ) -> Result<crate::LocalResult<LocalTimeType>, Error> {
+ self.as_ref().find_local_time_type_from_local(local_time, year)
+ }
+
+ /// 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<TransitionRule>,
+}
+
+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: i64,
+ year: i32,
+ ) -> Result<crate::LocalResult<LocalTimeType>, 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;
+
+ // if we have at least one transition,
+ // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::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 + i64::from(after_ltt.ut_offset);
+ let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset);
+
+ match transition_start.cmp(&transition_end) {
+ Ordering::Greater => {
+ // bakwards 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::LocalResult::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::LocalResult::Ambiguous(prev, after_ltt));
+ } else {
+ return Ok(crate::LocalResult::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::LocalResult::Single(prev));
+ } else if local_leap_time == transition_end {
+ if prev.ut_offset < after_ltt.ut_offset {
+ return Ok(crate::LocalResult::Ambiguous(prev, after_ltt));
+ } else {
+ return Ok(crate::LocalResult::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::LocalResult::Single(prev));
+ } else if local_leap_time < transition_end {
+ return Ok(crate::LocalResult::None);
+ } else if local_leap_time == transition_end {
+ return Ok(crate::LocalResult::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, year) {
+ Ok(local_time_type) => Ok(local_time_type),
+ Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
+ err => err,
+ }
+ } else {
+ Ok(crate::LocalResult::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<i64, Error> {
+ 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<i64, Error> {
+ if unix_leap_time == i64::min_value() {
+ 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; 8],
+}
+
+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.
+ fn new(input: &[u8]) -> Result<Self, Error> {
+ let len = input.len();
+
+ if !(3..=7).contains(&len) {
+ return Err(Error::LocalTimeType(
+ "time zone name must have between 3 and 7 characters",
+ ));
+ }
+
+ let mut bytes = [0; 8];
+ bytes[0] = input.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'-' => {}
+ _ => 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] {
+ match self.bytes[0] {
+ 3 => &self.bytes[1..4],
+ 4 => &self.bytes[1..5],
+ 5 => &self.bytes[1..6],
+ 6 => &self.bytes[1..7],
+ 7 => &self.bytes[1..8],
+ _ => unreachable!(),
+ }
+ }
+
+ /// Check if two time zone names are equal
+ fn equal(&self, other: &Self) -> bool {
+ self.bytes == other.bytes
+ }
+}
+
+impl AsRef<str> 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<TimeZoneName>,
+}
+
+impl LocalTimeType {
+ /// Construct a local time type
+ pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error> {
+ if ut_offset == i32::min_value() {
+ 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<Self, Error> {
+ if ut_offset == i32::min_value() {
+ 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<Path>) -> Result<File, Error> {
+ // 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(_))));
+ assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
+ 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_value().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!(TimeZone::from_posix_tz("").is_err());
+
+ 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_value(), 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_value(), 0)],
+ vec![LocalTimeType::UTC],
+ vec![LeapSecond::new(0, 1)],
+ None,
+ )?;
+ assert!(matches!(
+ time_zone.find_local_time_type(i64::max_value()),
+ Err(Error::FindLocalTimeType(_))
+ ));
+
+ Ok(())
+ }
+}
diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs
new file mode 100644
index 0000000..ce96a6e
--- /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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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::{Datelike, LocalResult};
+
+pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ offset(utc, false)
+}
+
+pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult<FixedOffset> {
+ offset(local, true)
+}
+
+fn offset(d: &NaiveDateTime, local: bool) -> LocalResult<FixedOffset> {
+ 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 initalized in a static context.
+thread_local! {
+ static TZ_INFO: RefCell<Option<Cache>> = 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")))]
+const TZDB_LOCATION: &str = "/usr/share/zoneinfo";
+
+fn fallback_timezone() -> Option<TimeZone> {
+ let tz_name = iana_time_zone::get_timezone().ok()?;
+ #[cfg(not(target_os = "android"))]
+ let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?;
+ #[cfg(target_os = "android")]
+ let bytes = android_tzdata::find_tz_data(&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) -> LocalResult<FixedOffset> {
+ 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.timestamp())
+ .expect("unable to select local time type")
+ .offset();
+
+ return match FixedOffset::east_opt(offset) {
+ Some(offset) => LocalResult::Single(offset),
+ None => LocalResult::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 LocalResult::None anyway.
+ self.zone
+ .find_local_time_type_from_local(d.timestamp(), d.year())
+ .expect("unable to select local time type")
+ .map(|o| FixedOffset::east_opt(o.offset()).unwrap())
+ }
+}
diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs
new file mode 100644
index 0000000..7574fb3
--- /dev/null
+++ b/src/offset/local/win_bindings.rs
@@ -0,0 +1,71 @@
+// Bindings generated by `windows-bindgen` 0.52.0
+
+#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)]
+::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL);
+::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL);
+::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL);
+::windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL);
+pub type BOOL = i32;
+pub type BOOLEAN = u8;
+#[repr(C)]
+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: BOOLEAN,
+}
+impl ::core::marker::Copy for DYNAMIC_TIME_ZONE_INFORMATION {}
+impl ::core::clone::Clone for DYNAMIC_TIME_ZONE_INFORMATION {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+#[repr(C)]
+pub struct FILETIME {
+ pub dwLowDateTime: u32,
+ pub dwHighDateTime: u32,
+}
+impl ::core::marker::Copy for FILETIME {}
+impl ::core::clone::Clone for FILETIME {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+#[repr(C)]
+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,
+}
+impl ::core::marker::Copy for SYSTEMTIME {}
+impl ::core::clone::Clone for SYSTEMTIME {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+#[repr(C)]
+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 ::core::marker::Copy for TIME_ZONE_INFORMATION {}
+impl ::core::clone::Clone for TIME_ZONE_INFORMATION {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt
new file mode 100644
index 0000000..7fb3e2f
--- /dev/null
+++ b/src/offset/local/win_bindings.txt
@@ -0,0 +1,7 @@
+--out src/offset/local/win_bindings.rs
+--config flatten sys
+--filter
+ Windows.Win32.System.Time.GetTimeZoneInformationForYear
+ Windows.Win32.System.Time.SystemTimeToFileTime
+ Windows.Win32.System.Time.SystemTimeToTzSpecificLocalTime
+ Windows.Win32.System.Time.TzSpecificLocalTimeToSystemTime
diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs
new file mode 100644
index 0000000..cee09ec
--- /dev/null
+++ b/src/offset/local/windows.rs
@@ -0,0 +1,262 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::cmp::Ordering;
+use std::convert::TryFrom;
+use std::mem::MaybeUninit;
+use std::ptr;
+
+use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION};
+
+use crate::offset::local::{lookup_with_dst_transitions, Transition};
+use crate::{Datelike, FixedOffset, LocalResult, 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) -> LocalResult<FixedOffset> {
+ // 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 LocalResult::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,
+ };
+ LocalResult::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) -> LocalResult<FixedOffset> {
+ let tz_info = match TzInfo::for_year(local.year()) {
+ Some(tz_info) => tz_info,
+ None => return LocalResult::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 LocalResult::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) => return LocalResult::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<NaiveDateTime>,
+ // Transition from daylight saving time to standard time, given in local daylight saving time.
+ dst_transition: Option<NaiveDateTime>,
+}
+
+impl TzInfo {
+ fn for_year(year: i32) -> Option<TzInfo> {
+ // 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::<TIME_ZONE_INFORMATION>::uninit();
+ if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 {
+ return None;
+ }
+ tz_info.assume_init()
+ };
+ Some(TzInfo {
+ std_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.StandardBias) * 60)?,
+ dst_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.DaylightBias) * 60)?,
+ std_transition: system_time_from_naive_date_time(tz_info.StandardDate, year),
+ dst_transition: system_time_from_naive_date_time(tz_info.DaylightDate, year),
+ })
+ }
+}
+
+fn system_time_from_naive_date_time(st: SYSTEMTIME, year: i32) -> Option<NaiveDateTime> {
+ if st.wYear == 0 && st.wMonth == 0 {
+ return None; // No DST transitions for this year in this timezone.
+ }
+ let time = NaiveTime::from_hms_milli_opt(
+ st.wHour as u32,
+ st.wMinute as u32,
+ st.wSecond as u32,
+ st.wMilliseconds as u32,
+ )?;
+ // In Chrono's Weekday, Monday is 0 whereas in SYSTEMTIME Monday is 1 and Sunday is 0.
+ // Therefore we move back one day after converting the u16 value to a Weekday.
+ let day_of_week = Weekday::try_from(u8::try_from(st.wDayOfWeek).ok()?).ok()?.pred();
+ if st.wYear != 0 {
+ return NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32)
+ .map(|d| d.and_time(time));
+ }
+ let date = if let Some(date) =
+ NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, st.wDay as u8)
+ {
+ date
+ } else if st.wDay == 5 {
+ NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, 4)?
+ } else {
+ return None;
+ };
+ Some(date.and_time(time))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::offset::local::win_bindings::{
+ SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, FILETIME, SYSTEMTIME,
+ };
+ use crate::{DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime};
+ 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<Local> {
+ 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::<SYSTEMTIME>::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::<FILETIME>::uninit();
+ unsafe {
+ SystemTimeToFileTime(st, init.as_mut_ptr());
+ }
+ // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is
+ // initalized.
+ 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 += Duration::hours(1);
+ }
+ }
+}
diff --git a/src/offset/mod.rs b/src/offset/mod.rs
index 0da6bfb..11928b4 100644
--- a/src/offset/mod.rs
+++ b/src/offset/mod.rs
@@ -20,10 +20,22 @@
use core::fmt;
-use format::{parse, ParseResult, Parsed, StrftimeItems};
-use naive::{NaiveDate, NaiveDateTime, NaiveTime};
-use Weekday;
-use {Date, DateTime};
+use crate::format::{parse, ParseResult, Parsed, StrftimeItems};
+use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
+use crate::Weekday;
+#[allow(deprecated)]
+use crate::{Date, DateTime};
+
+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 conversion result from the local time to the timezone-aware datetime types.
#[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)]
@@ -40,6 +52,7 @@ pub enum LocalResult<T> {
impl<T> LocalResult<T> {
/// Returns `Some` only when the conversion result is unique, or `None` otherwise.
+ #[must_use]
pub fn single(self) -> Option<T> {
match self {
LocalResult::Single(t) => Some(t),
@@ -48,6 +61,7 @@ impl<T> LocalResult<T> {
}
/// Returns `Some` for the earliest possible conversion result, or `None` if none.
+ #[must_use]
pub fn earliest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t),
@@ -56,6 +70,7 @@ impl<T> LocalResult<T> {
}
/// Returns `Some` for the latest possible conversion result, or `None` if none.
+ #[must_use]
pub fn latest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t),
@@ -64,6 +79,7 @@ impl<T> LocalResult<T> {
}
/// Maps a `LocalResult<T>` into `LocalResult<U>` with given function.
+ #[must_use]
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> LocalResult<U> {
match self {
LocalResult::None => LocalResult::None,
@@ -73,12 +89,14 @@ impl<T> LocalResult<T> {
}
}
+#[allow(deprecated)]
impl<Tz: TimeZone> LocalResult<Date<Tz>> {
/// 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]
+ #[must_use]
pub fn and_time(self, time: NaiveTime) -> LocalResult<DateTime<Tz>> {
match self {
LocalResult::Single(d) => {
@@ -93,6 +111,7 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
+ #[must_use]
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> {
match self {
LocalResult::Single(d) => {
@@ -108,6 +127,7 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
+ #[must_use]
pub fn and_hms_milli_opt(
self,
hour: u32,
@@ -129,6 +149,7 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
+ #[must_use]
pub fn and_hms_micro_opt(
self,
hour: u32,
@@ -150,6 +171,7 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
+ #[must_use]
pub fn and_hms_nano_opt(
self,
hour: u32,
@@ -168,6 +190,8 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
impl<T: fmt::Debug> LocalResult<T> {
/// Returns the single unique conversion result, or panics accordingly.
+ #[must_use]
+ #[track_caller]
pub fn unwrap(self) -> T {
match self {
LocalResult::None => panic!("No such local time"),
@@ -187,14 +211,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 `LocalResult::None` on invalid input data.
+ fn with_ymd_and_hms(
+ &self,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ min: u32,
+ sec: u32,
+ ) -> LocalResult<DateTime<Self>> {
+ 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 => LocalResult::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 +246,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> {
self.ymd_opt(year, month, day).unwrap()
}
@@ -221,15 +259,8 @@ 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);
- /// ~~~~
+ #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")]
+ #[allow(deprecated)]
fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult<Date<Self>> {
match NaiveDate::from_ymd_opt(year, month, day) {
Some(d) => self.from_local_date(&d),
@@ -244,14 +275,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> {
self.yo_opt(year, ordinal).unwrap()
}
@@ -263,6 +291,11 @@ 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.
+ #[deprecated(
+ since = "0.4.23",
+ note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
+ )]
+ #[allow(deprecated)]
fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult<Date<Self>> {
match NaiveDate::from_yo_opt(year, ordinal) {
Some(d) => self.from_local_date(&d),
@@ -279,14 +312,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> {
self.isoywd_opt(year, week, weekday).unwrap()
}
@@ -300,6 +330,11 @@ 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.
+ #[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) -> LocalResult<Date<Self>> {
match NaiveDate::from_isoywd_opt(year, week, weekday) {
Some(d) => self.from_local_date(&d),
@@ -311,16 +346,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> {
self.timestamp_opt(secs, nsecs).unwrap()
}
@@ -329,8 +363,22 @@ 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.
///
+ /// 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 `LocalResult::None` on out-of-range number of seconds and/or
/// invalid nanosecond, otherwise always returns `LocalResult::Single`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use chrono::{Utc, TimeZone};
+ ///
+ /// 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) -> LocalResult<DateTime<Self>> {
match NaiveDateTime::from_timestamp_opt(secs, nsecs) {
Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
@@ -343,14 +391,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> {
self.timestamp_millis_opt(millis).unwrap()
}
@@ -365,35 +406,32 @@ pub trait TimeZone: Sized + Clone {
///
/// # Example
///
- /// ~~~~
+ /// ```
/// use chrono::{Utc, TimeZone, LocalResult};
/// match Utc.timestamp_millis_opt(1431648000) {
/// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648),
/// _ => panic!("Incorrect timestamp_millis"),
/// };
- /// ~~~~
+ /// ```
fn timestamp_millis_opt(&self, millis: i64) -> LocalResult<DateTime<Self>> {
- let (mut secs, mut millis) = (millis / 1000, millis % 1000);
- if millis < 0 {
- secs -= 1;
- millis += 1000;
+ match NaiveDateTime::from_timestamp_millis(millis) {
+ Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
+ None => LocalResult::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};
///
/// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648);
- /// ~~~~
+ /// ```
fn timestamp_nanos(&self, nanos: i64) -> DateTime<Self> {
let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000);
if nanos < 0 {
@@ -403,16 +441,42 @@ pub trait TimeZone: Sized + Clone {
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](../format/strftime/index.html)
- /// on the supported escape sequences.
+ /// 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::{Utc, TimeZone};
+ ///
+ /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648);
+ /// ```
+ fn timestamp_micros(&self, micros: i64) -> LocalResult<DateTime<Self>> {
+ match NaiveDateTime::from_timestamp_micros(micros) {
+ Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
+ None => LocalResult::None,
+ }
+ }
+
+ /// Parses a string with the specified format string and returns a
+ /// `DateTime` with the current offset.
+ ///
+ /// See the [`crate::format::strftime`] module on the
+ /// supported escape sequences.
+ ///
+ /// If the to-be-parsed string includes an offset, it *must* match the
+ /// offset of the TimeZone, otherwise an error will be returned.
///
- /// If the format does not include offsets, the current offset is assumed;
- /// otherwise the input should have a matching UTC offset.
+ /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with
+ /// parsed [`FixedOffset`].
///
- /// See also `DateTime::parse_from_str` which gives a local `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<DateTime<Self>> {
let mut parsed = Parsed::new();
parse(&mut parsed, s, StrftimeItems::new(fmt))?;
@@ -429,6 +493,9 @@ pub trait TimeZone: Sized + Clone {
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset>;
/// Converts the local `NaiveDate` to the timezone-aware `Date` if possible.
+ #[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) -> LocalResult<Date<Self>> {
self.offset_from_local_date(local).map(|offset| {
// since FixedOffset is within +/- 1 day, the date is never affected
@@ -437,9 +504,26 @@ pub trait TimeZone: Sized + Clone {
}
/// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible.
+ #[allow(clippy::wrong_self_convention)]
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> {
- self.offset_from_local_datetime(local)
- .map(|offset| DateTime::from_utc(*local - offset.fix(), offset))
+ // Return `LocalResult::None` when the offset pushes a value out of range, instead of
+ // panicking.
+ match self.offset_from_local_datetime(local) {
+ LocalResult::None => LocalResult::None,
+ LocalResult::Single(offset) => match local.checked_sub_offset(offset.fix()) {
+ Some(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)),
+ None => LocalResult::None,
+ },
+ LocalResult::Ambiguous(o1, o2) => {
+ match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) {
+ (Some(d1), Some(d2)) => LocalResult::Ambiguous(
+ DateTime::from_naive_utc_and_offset(d1, o1),
+ DateTime::from_naive_utc_and_offset(d2, o2),
+ ),
+ _ => LocalResult::None,
+ }
+ }
+ }
}
/// Creates the offset for given UTC `NaiveDate`. This cannot fail.
@@ -450,48 +534,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<Self> {
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<Self> {
- 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, LocalResult::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, LocalResult::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 &[
@@ -528,4 +632,18 @@ mod tests {
Utc.timestamp_nanos(i64::default());
Utc.timestamp_nanos(i64::min_value());
}
+
+ #[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 aec6667..29b832a 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_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+ ))
))]
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, LocalResult, 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).
@@ -24,35 +32,81 @@ use {Date, DateTime};
///
/// # Example
///
-/// ~~~~
-/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc};
+/// ```
+/// use chrono::{TimeZone, NaiveDateTime, Utc};
///
-/// let dt = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc);
+/// let dt = Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_opt(61, 0).unwrap());
///
-/// assert_eq!(Utc.timestamp(61, 0), dt);
-/// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt);
-/// ~~~~
-#[derive(Copy, Clone, PartialEq, Eq)]
+/// 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, 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> {
Utc::now().date()
}
- /// Returns a `DateTime` which corresponds to the current date.
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
+ /// Returns a `DateTime<Utc>` which corresponds to the current date and time in UTC.
+ ///
+ /// See also the similar [`Local::now()`] which returns `DateTime<Local>`, 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_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+ )))]
+ #[must_use]
pub fn now() -> DateTime<Utc> {
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)
+ let naive =
+ NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap();
+ Utc.from_utc_datetime(&naive)
}
- /// Returns a `DateTime` which corresponds to the current date.
- #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
+ /// Returns a `DateTime` which corresponds to the current date and time.
+ #[cfg(all(
+ target_arch = "wasm32",
+ feature = "wasmbind",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+ ))]
+ #[must_use]
pub fn now() -> DateTime<Utc> {
let now = js_sys::Date::new_0();
DateTime::<Utc>::from(now)
@@ -83,7 +137,7 @@ impl TimeZone for Utc {
impl Offset for Utc {
fn fix(&self) -> FixedOffset {
- FixedOffset::east(0)
+ FixedOffset::east_opt(0).unwrap()
}
}
diff --git a/src/oldtime.rs b/src/oldtime.rs
deleted file mode 100644
index 8656769..0000000
--- a/src/oldtime.rs
+++ /dev/null
@@ -1,684 +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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<i64> {
- 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<i64> {
- 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<Duration> {
- 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<Duration> {
- 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<Duration, OutOfRangeError> {
- // 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<StdDuration, OutOfRangeError> {
- 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<i32> 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<i32> 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 {
- 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 92d7c3a..315d453 100644
--- a/src/round.rs
+++ b/src/round.rs
@@ -1,16 +1,13 @@
// 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 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
@@ -25,8 +22,8 @@ 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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap();
/// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000);
/// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000);
/// ```
@@ -37,8 +34,8 @@ 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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap();
/// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000);
/// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000);
/// ```
@@ -47,7 +44,7 @@ pub trait SubsecRound {
impl<T> SubsecRound for T
where
- T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>,
+ T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
{
fn round_subsecs(self, digits: u16) -> T {
let span = span_for_digits(digits);
@@ -55,9 +52,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
@@ -68,7 +65,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
}
@@ -76,7 +73,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,
@@ -92,139 +89,159 @@ 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.
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(not(feature = "std"))]
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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap();
/// assert_eq!(
- /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(),
+ /// dt.duration_round(TimeDelta::milliseconds(10)).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::days(1)).unwrap().to_string(),
/// "2018-01-12 00:00:00 UTC"
/// );
/// ```
- fn duration_round(self, duration: Duration) -> Result<Self, Self::Err>;
+ fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>;
- /// 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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap();
/// assert_eq!(
- /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(),
+ /// dt.duration_trunc(TimeDelta::milliseconds(10)).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::days(1)).unwrap().to_string(),
/// "2018-01-11 00:00:00 UTC"
/// );
/// ```
- fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err>;
+ fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>;
}
-/// 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;
-
impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
type Err = RoundingError;
- fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> {
- if let Some(span) = duration.num_nanoseconds() {
- if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
- return Err(RoundingError::TimestampExceedsLimit);
- }
- let stamp = self.timestamp_nanos();
- if span > stamp.abs() {
- return Err(RoundingError::DurationExceedsTimestamp);
- }
- let delta_down = stamp % span;
- if delta_down == 0 {
- Ok(self)
+ fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
+ duration_round(self.naive_local(), self, duration)
+ }
+
+ fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
+ duration_trunc(self.naive_local(), self, duration)
+ }
+}
+
+impl DurationRound for NaiveDateTime {
+ type Err = RoundingError;
+
+ fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
+ duration_round(self, self, duration)
+ }
+
+ fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
+ duration_trunc(self, self, duration)
+ }
+}
+
+fn duration_round<T>(
+ naive: NaiveDateTime,
+ original: T,
+ duration: TimeDelta,
+) -> Result<T, RoundingError>
+where
+ T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
+{
+ if let Some(span) = duration.num_nanoseconds() {
+ if span < 0 {
+ return Err(RoundingError::DurationExceedsLimit);
+ }
+ let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
+ if span == 0 {
+ return Ok(original);
+ }
+ let delta_down = stamp % span;
+ if delta_down == 0 {
+ Ok(original)
+ } else {
+ let (delta_up, delta_down) = if delta_down < 0 {
+ (delta_down.abs(), span - delta_down.abs())
+ } else {
+ (span - delta_down, delta_down)
+ };
+ if delta_up <= delta_down {
+ Ok(original + TimeDelta::nanoseconds(delta_up))
} else {
- let (delta_up, delta_down) = if delta_down < 0 {
- (delta_down.abs(), span - delta_down.abs())
- } else {
- (span - delta_down, delta_down)
- };
- if delta_up <= delta_down {
- Ok(self + Duration::nanoseconds(delta_up))
- } else {
- Ok(self - Duration::nanoseconds(delta_down))
- }
+ Ok(original - TimeDelta::nanoseconds(delta_down))
}
- } else {
- Err(RoundingError::DurationExceedsLimit)
}
+ } else {
+ Err(RoundingError::DurationExceedsLimit)
}
+}
- fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> {
- if let Some(span) = duration.num_nanoseconds() {
- if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
- return Err(RoundingError::TimestampExceedsLimit);
- }
- let stamp = self.timestamp_nanos();
- if span > stamp.abs() {
- return Err(RoundingError::DurationExceedsTimestamp);
- }
- let delta_down = stamp % span;
- match delta_down.cmp(&0) {
- Ordering::Equal => Ok(self),
- Ordering::Greater => Ok(self - Duration::nanoseconds(delta_down)),
- Ordering::Less => Ok(self - Duration::nanoseconds(span - delta_down.abs())),
- }
- } else {
- Err(RoundingError::DurationExceedsLimit)
+fn duration_trunc<T>(
+ naive: NaiveDateTime,
+ original: T,
+ duration: TimeDelta,
+) -> Result<T, RoundingError>
+where
+ T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
+{
+ if let Some(span) = duration.num_nanoseconds() {
+ if span < 0 {
+ return Err(RoundingError::DurationExceedsLimit);
+ }
+ let stamp = naive.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())),
}
+ } else {
+ Err(RoundingError::DurationExceedsLimit)
}
}
-/// An error from rounding by `Duration`
+/// An error from rounding by `TimeDelta`
///
/// See: [`DurationRound`]
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum RoundingError {
- /// Error when the Duration exceeds the Duration from or until the Unix epoch.
+ /// Error when the TimeDelta exceeds the TimeDelta 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);
- ///
- /// 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, Utc, NaiveDate};
+ /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap();
///
/// assert_eq!(
- /// dt.duration_round(Duration::days(300 * 365)),
+ /// dt.duration_round(TimeDelta::days(300 * 365)),
/// Err(RoundingError::DurationExceedsLimit)
/// );
/// ```
@@ -233,10 +250,10 @@ 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::days(1)), Err(RoundingError::TimestampExceedsLimit),);
/// ```
TimestampExceedsLimit,
}
@@ -257,7 +274,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 {
@@ -267,30 +284,45 @@ impl std::error::Error for RoundingError {
#[cfg(test)]
mod tests {
- use super::{Duration, DurationRound, SubsecRound};
- use offset::{FixedOffset, TimeZone, Utc};
- use Timelike;
+ use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
+ use crate::offset::{FixedOffset, TimeZone, Utc};
+ use crate::Timelike;
+ use crate::{NaiveDate, NaiveDateTime};
#[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, 084_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);
- assert_eq!(dt.round_subsecs(8).nanosecond(), 084_660_680);
- assert_eq!(dt.round_subsecs(7).nanosecond(), 084_660_700);
- assert_eq!(dt.round_subsecs(6).nanosecond(), 084_661_000);
- assert_eq!(dt.round_subsecs(5).nanosecond(), 084_660_000);
- assert_eq!(dt.round_subsecs(4).nanosecond(), 084_700_000);
- assert_eq!(dt.round_subsecs(3).nanosecond(), 085_000_000);
- assert_eq!(dt.round_subsecs(2).nanosecond(), 080_000_000);
+ assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680);
+ assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700);
+ assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000);
+ assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000);
+ assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000);
+ assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000);
+ assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000);
assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
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);
@@ -303,7 +335,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);
@@ -316,24 +355,38 @@ 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, 084_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);
- assert_eq!(dt.trunc_subsecs(8).nanosecond(), 084_660_680);
- assert_eq!(dt.trunc_subsecs(7).nanosecond(), 084_660_600);
- assert_eq!(dt.trunc_subsecs(6).nanosecond(), 084_660_000);
- assert_eq!(dt.trunc_subsecs(5).nanosecond(), 084_660_000);
- assert_eq!(dt.trunc_subsecs(4).nanosecond(), 084_600_000);
- assert_eq!(dt.trunc_subsecs(3).nanosecond(), 084_000_000);
- assert_eq!(dt.trunc_subsecs(2).nanosecond(), 080_000_000);
+ assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680);
+ assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600);
+ assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000);
+ assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000);
+ assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000);
+ assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000);
+ assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000);
assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
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);
@@ -346,7 +399,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);
@@ -359,98 +419,379 @@ 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::zero()).unwrap().to_string(),
+ "2016-12-31 23:59:59.175500 UTC"
+ );
assert_eq!(
- dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(),
+ dt.duration_round(TimeDelta::milliseconds(10)).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::minutes(5)).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::minutes(5)).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::minutes(10)).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::minutes(30)).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::hours(1)).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::days(1)).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(TimeDelta::days(1)).unwrap().to_string(),
+ "2020-10-28 00:00:00 +01:00"
+ );
+ assert_eq!(
+ dt.duration_round(TimeDelta::weeks(1)).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(TimeDelta::days(1)).unwrap().to_string(),
+ "2020-10-28 00:00:00 -01:00"
+ );
+ assert_eq!(
+ dt.duration_round(TimeDelta::weeks(1)).unwrap().to_string(),
+ "2020-10-29 00:00:00 -01:00"
+ );
+ }
+
+ #[test]
+ fn test_duration_round_naive() {
+ 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::zero()).unwrap().to_string(),
+ "2016-12-31 23:59:59.175500"
+ );
+
+ assert_eq!(
+ dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(),
+ "2016-12-31 23:59:59.180"
+ );
+
+ // round up
+ 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(TimeDelta::minutes(5)).unwrap().to_string(),
+ "2012-12-12 18:25:00"
+ );
+ // round down
+ 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(TimeDelta::minutes(5)).unwrap().to_string(),
+ "2012-12-12 18:20:00"
+ );
+
+ assert_eq!(
+ dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(),
+ "2012-12-12 18:20:00"
+ );
+ assert_eq!(
+ dt.duration_round(TimeDelta::minutes(30)).unwrap().to_string(),
+ "2012-12-12 18:30:00"
+ );
+ assert_eq!(
+ dt.duration_round(TimeDelta::hours(1)).unwrap().to_string(),
+ "2012-12-12 18:00:00"
+ );
+ assert_eq!(
+ dt.duration_round(TimeDelta::days(1)).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::minutes(10)).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, 1_75_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::milliseconds(10)).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::minutes(5)).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::minutes(5)).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::minutes(10)).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::minutes(30)).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::hours(1)).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::days(1)).unwrap().to_string(),
"2012-12-12 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_trunc(TimeDelta::days(1)).unwrap().to_string(),
+ "2020-10-27 00:00:00 +01:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(),
+ "2020-10-22 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_trunc(TimeDelta::days(1)).unwrap().to_string(),
+ "2020-10-27 00:00:00 -01:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(),
+ "2020-10-22 00:00:00 -01:00"
+ );
+ }
+
+ #[test]
+ fn test_duration_trunc_naive() {
+ 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::milliseconds(10)).unwrap().to_string(),
+ "2016-12-31 23:59:59.170"
+ );
+
+ // would round up
+ 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(TimeDelta::minutes(5)).unwrap().to_string(),
+ "2012-12-12 18:20:00"
+ );
+ // would round down
+ 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(TimeDelta::minutes(5)).unwrap().to_string(),
+ "2012-12-12 18:20:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(),
+ "2012-12-12 18:20:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::minutes(30)).unwrap().to_string(),
+ "2012-12-12 18:00:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::hours(1)).unwrap().to_string(),
+ "2012-12-12 18:00:00"
+ );
+ assert_eq!(
+ dt.duration_trunc(TimeDelta::days(1)).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::minutes(10)).unwrap().to_string(),
"1969-12-12 12:10:00 UTC"
);
}
+
+ #[test]
+ fn issue1010() {
+ let dt = NaiveDateTime::from_timestamp_opt(-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 = NaiveDateTime::from_timestamp_opt(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 = NaiveDateTime::from_timestamp_opt(-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::minutes(15);
+
+ 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::minutes(15);
+
+ 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 = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 - 1).unwrap();
+ assert_eq!(dt.duration_round(span).unwrap().to_string(), "1677-09-21 00:12:43.145224193");
+
+ let dt = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 + 1).unwrap();
+ assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
+
+ let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 + 1).unwrap();
+ assert_eq!(dt.duration_round(span).unwrap().to_string(), "2262-04-11 23:47:16.854775807");
+
+ let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 - 1).unwrap();
+ assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
+ }
}
diff --git a/src/sys.rs b/src/sys.rs
deleted file mode 100644
index 2e46b7e..0000000
--- 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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(unix)]
-#[path = "sys/unix.rs"]
-mod inner;
-
-#[cfg(windows)]
-#[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, 10<sup>9</sup> - 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 9172a85..0000000
--- 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 2f845e7..0000000
--- 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 3f90338..0000000
--- 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 0000000..e5e22b6
--- /dev/null
+++ b/src/time_delta.rs
@@ -0,0 +1,1215 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Temporal quantification
+
+use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
+use core::time::Duration;
+use core::{fmt, i64};
+#[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))]
+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<TimeDelta> {
+ 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]
+ 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::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> {
+ 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]
+ 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::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> {
+ 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]
+ 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::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> {
+ 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]
+ 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::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> {
+ 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]
+ 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> {
+ 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]
+ 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<TimeDelta> {
+ // 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`.
+ 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 number of nanoseconds such that
+ /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` 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
+ }
+ }
+
+ /// 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 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<i64> {
+ 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 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<i64> {
+ 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)
+ }
+
+ /// Add two `TimeDelta`s, returning `None` if overflow occurred.
+ #[must_use]
+ pub const fn checked_add(&self, rhs: &TimeDelta) -> Option<TimeDelta> {
+ // 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<TimeDelta> {
+ // 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)
+ }
+
+ /// 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.
+ #[inline]
+ pub const fn min_value() -> TimeDelta {
+ MIN
+ }
+
+ /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds.
+ #[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<TimeDelta, OutOfRangeError> {
+ // 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<Duration, OutOfRangeError> {
+ 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 }
+ }
+}
+
+impl Neg for TimeDelta {
+ type Output = TimeDelta;
+
+ #[inline]
+ 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;
+
+ fn add(self, rhs: TimeDelta) -> TimeDelta {
+ self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed")
+ }
+}
+
+impl Sub for TimeDelta {
+ type Output = TimeDelta;
+
+ fn sub(self, rhs: TimeDelta) -> TimeDelta {
+ self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed")
+ }
+}
+
+impl AddAssign for TimeDelta {
+ fn add_assign(&mut self, rhs: TimeDelta) {
+ let new = self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed");
+ *self = new;
+ }
+}
+
+impl SubAssign for TimeDelta {
+ fn sub_assign(&mut self, rhs: TimeDelta) {
+ let new = self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed");
+ *self = new;
+ }
+}
+
+impl Mul<i32> for TimeDelta {
+ type Output = TimeDelta;
+
+ fn mul(self, rhs: i32) -> TimeDelta {
+ // 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;
+ TimeDelta { secs, nanos: nanos as i32 }
+ }
+}
+
+impl Div<i32> for TimeDelta {
+ type Output = TimeDelta;
+
+ fn div(self, rhs: i32) -> TimeDelta {
+ 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;
+ }
+ TimeDelta { secs, nanos }
+ }
+}
+
+impl<'a> core::iter::Sum<&'a TimeDelta> for TimeDelta {
+ fn sum<I: Iterator<Item = &'a TimeDelta>>(iter: I) -> TimeDelta {
+ iter.fold(TimeDelta::zero(), |acc, x| acc + *x)
+ }
+}
+
+impl core::iter::Sum<TimeDelta> for TimeDelta {
+ fn sum<I: Iterator<Item = TimeDelta>>(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, "{}P", sign)?;
+ // 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!(".{:01$}", fraction_digits, figures))?;
+ }
+ 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)]
+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(feature = "std")]
+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<TimeDelta> {
+ 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(test)]
+mod tests {
+ use super::OutOfRangeError;
+ use super::{TimeDelta, MAX, MIN};
+ use core::time::Duration;
+
+ #[test]
+ fn test_duration() {
+ assert!(TimeDelta::seconds(1) != TimeDelta::zero());
+ assert_eq!(TimeDelta::seconds(1) + TimeDelta::seconds(2), TimeDelta::seconds(3));
+ assert_eq!(
+ TimeDelta::seconds(86_399) + TimeDelta::seconds(4),
+ TimeDelta::days(1) + TimeDelta::seconds(3)
+ );
+ assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000));
+ assert_eq!(
+ TimeDelta::days(10) - TimeDelta::seconds(1_000_000),
+ TimeDelta::seconds(-136_000)
+ );
+ assert_eq!(
+ TimeDelta::days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890),
+ TimeDelta::days(3) + TimeDelta::nanoseconds(234_567_890)
+ );
+ assert_eq!(-TimeDelta::days(3), TimeDelta::days(-3));
+ assert_eq!(
+ -(TimeDelta::days(3) + TimeDelta::seconds(70)),
+ TimeDelta::days(-4) + TimeDelta::seconds(86_400 - 70)
+ );
+
+ let mut d = TimeDelta::default();
+ d += TimeDelta::minutes(1);
+ d -= TimeDelta::seconds(30);
+ assert_eq!(d, TimeDelta::seconds(30));
+ }
+
+ #[test]
+ fn test_duration_num_days() {
+ assert_eq!(TimeDelta::zero().num_days(), 0);
+ assert_eq!(TimeDelta::days(1).num_days(), 1);
+ assert_eq!(TimeDelta::days(-1).num_days(), -1);
+ assert_eq!(TimeDelta::seconds(86_399).num_days(), 0);
+ assert_eq!(TimeDelta::seconds(86_401).num_days(), 1);
+ assert_eq!(TimeDelta::seconds(-86_399).num_days(), 0);
+ assert_eq!(TimeDelta::seconds(-86_401).num_days(), -1);
+ assert_eq!(TimeDelta::days(i32::MAX as i64).num_days(), i32::MAX as i64);
+ assert_eq!(TimeDelta::days(i32::MIN as i64).num_days(), i32::MIN as i64);
+ }
+
+ #[test]
+ fn test_duration_num_seconds() {
+ assert_eq!(TimeDelta::zero().num_seconds(), 0);
+ assert_eq!(TimeDelta::seconds(1).num_seconds(), 1);
+ assert_eq!(TimeDelta::seconds(-1).num_seconds(), -1);
+ assert_eq!(TimeDelta::milliseconds(999).num_seconds(), 0);
+ assert_eq!(TimeDelta::milliseconds(1001).num_seconds(), 1);
+ assert_eq!(TimeDelta::milliseconds(-999).num_seconds(), 0);
+ assert_eq!(TimeDelta::milliseconds(-1001).num_seconds(), -1);
+ }
+ #[test]
+ fn test_duration_seconds_max_allowed() {
+ let duration = TimeDelta::seconds(i64::MAX / 1_000);
+ 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::seconds(i64::MIN / 1_000); // 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_num_milliseconds() {
+ assert_eq!(TimeDelta::zero().num_milliseconds(), 0);
+ assert_eq!(TimeDelta::milliseconds(1).num_milliseconds(), 1);
+ assert_eq!(TimeDelta::milliseconds(-1).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::milliseconds(i64::MAX);
+ 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::milliseconds(i64::MAX)
+ .checked_add(&TimeDelta::milliseconds(1))
+ .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::milliseconds(-i64::MAX);
+ 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::milliseconds(-i64::MAX)
+ .checked_sub(&TimeDelta::milliseconds(1))
+ .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::days(i64::MAX / MICROS_PER_DAY).num_microseconds(),
+ Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY)
+ );
+ assert_eq!(
+ TimeDelta::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(),
+ Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY)
+ );
+ assert_eq!(TimeDelta::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None);
+ assert_eq!(TimeDelta::days(-i64::MAX / MICROS_PER_DAY - 1).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::milliseconds(i64::MAX);
+ 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::milliseconds(i64::MAX)
+ .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::milliseconds(-i64::MAX);
+ 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::milliseconds(-i64::MAX)
+ .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::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(),
+ Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY)
+ );
+ assert_eq!(
+ TimeDelta::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(),
+ Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY)
+ );
+ assert_eq!(TimeDelta::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None);
+ assert_eq!(TimeDelta::days(-i64::MAX / NANOS_PER_DAY - 1).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::milliseconds(i64::MAX);
+ 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::milliseconds(i64::MAX)
+ .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::milliseconds(-i64::MAX);
+ 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::milliseconds(-i64::MAX)
+ .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::milliseconds(i64::MAX));
+ 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::milliseconds(-i64::MAX));
+ assert_eq!(MIN.num_milliseconds(), -i64::MAX);
+ assert_eq!(MIN.num_microseconds(), None);
+ assert_eq!(MIN.num_nanoseconds(), None);
+ }
+
+ #[test]
+ fn test_duration_ord() {
+ assert!(TimeDelta::milliseconds(1) < TimeDelta::milliseconds(2));
+ assert!(TimeDelta::milliseconds(2) > TimeDelta::milliseconds(1));
+ assert!(TimeDelta::milliseconds(-1) > TimeDelta::milliseconds(-2));
+ assert!(TimeDelta::milliseconds(-2) < TimeDelta::milliseconds(-1));
+ assert!(TimeDelta::milliseconds(-1) < TimeDelta::milliseconds(1));
+ assert!(TimeDelta::milliseconds(1) > TimeDelta::milliseconds(-1));
+ assert!(TimeDelta::milliseconds(0) < TimeDelta::milliseconds(1));
+ assert!(TimeDelta::milliseconds(0) > TimeDelta::milliseconds(-1));
+ assert!(TimeDelta::milliseconds(1_001) < TimeDelta::milliseconds(1_002));
+ assert!(TimeDelta::milliseconds(-1_001) > TimeDelta::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!(TimeDelta::milliseconds(i64::MAX) > TimeDelta::milliseconds(i64::MAX - 1));
+ assert!(TimeDelta::milliseconds(-i64::MAX) < TimeDelta::milliseconds(-i64::MAX + 1));
+ }
+
+ #[test]
+ fn test_duration_checked_ops() {
+ assert_eq!(
+ TimeDelta::milliseconds(i64::MAX).checked_add(&TimeDelta::milliseconds(0)),
+ Some(TimeDelta::milliseconds(i64::MAX))
+ );
+ assert_eq!(
+ TimeDelta::milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)),
+ Some(TimeDelta::milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999))
+ );
+ assert!(TimeDelta::milliseconds(i64::MAX)
+ .checked_add(&TimeDelta::microseconds(1000))
+ .is_none());
+ assert!(TimeDelta::milliseconds(i64::MAX)
+ .checked_add(&TimeDelta::nanoseconds(1))
+ .is_none());
+
+ assert_eq!(
+ TimeDelta::milliseconds(-i64::MAX).checked_sub(&TimeDelta::milliseconds(0)),
+ Some(TimeDelta::milliseconds(-i64::MAX))
+ );
+ assert_eq!(
+ TimeDelta::milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)),
+ Some(TimeDelta::milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999))
+ );
+ assert!(TimeDelta::milliseconds(-i64::MAX)
+ .checked_sub(&TimeDelta::milliseconds(1))
+ .is_none());
+ assert!(TimeDelta::milliseconds(-i64::MAX)
+ .checked_sub(&TimeDelta::nanoseconds(1))
+ .is_none());
+ }
+
+ #[test]
+ fn test_duration_abs() {
+ assert_eq!(TimeDelta::milliseconds(1300).abs(), TimeDelta::milliseconds(1300));
+ assert_eq!(TimeDelta::milliseconds(1000).abs(), TimeDelta::milliseconds(1000));
+ assert_eq!(TimeDelta::milliseconds(300).abs(), TimeDelta::milliseconds(300));
+ assert_eq!(TimeDelta::milliseconds(0).abs(), TimeDelta::milliseconds(0));
+ assert_eq!(TimeDelta::milliseconds(-300).abs(), TimeDelta::milliseconds(300));
+ assert_eq!(TimeDelta::milliseconds(-700).abs(), TimeDelta::milliseconds(700));
+ assert_eq!(TimeDelta::milliseconds(-1000).abs(), TimeDelta::milliseconds(1000));
+ assert_eq!(TimeDelta::milliseconds(-1300).abs(), TimeDelta::milliseconds(1300));
+ assert_eq!(TimeDelta::milliseconds(-1700).abs(), TimeDelta::milliseconds(1700));
+ assert_eq!(TimeDelta::milliseconds(-i64::MAX).abs(), TimeDelta::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::seconds(1));
+ assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::seconds(1));
+ assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::seconds(1));
+ assert_eq!(
+ TimeDelta::nanoseconds(30) * 333_333_333,
+ TimeDelta::seconds(10) - TimeDelta::nanoseconds(10)
+ );
+ assert_eq!(
+ (TimeDelta::nanoseconds(1) + TimeDelta::seconds(1) + TimeDelta::days(1)) * 3,
+ TimeDelta::nanoseconds(3) + TimeDelta::seconds(3) + TimeDelta::days(3)
+ );
+ assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::seconds(-3));
+ assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::seconds(-3));
+ }
+
+ #[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::seconds(1) / 3, TimeDelta::nanoseconds(333_333_333));
+ assert_eq!(TimeDelta::seconds(4) / 3, TimeDelta::nanoseconds(1_333_333_333));
+ assert_eq!(TimeDelta::seconds(-1) / 2, TimeDelta::milliseconds(-500));
+ assert_eq!(TimeDelta::seconds(1) / -2, TimeDelta::milliseconds(-500));
+ assert_eq!(TimeDelta::seconds(-1) / -2, TimeDelta::milliseconds(500));
+ assert_eq!(TimeDelta::seconds(-4) / 3, TimeDelta::nanoseconds(-1_333_333_333));
+ assert_eq!(TimeDelta::seconds(-4) / -3, TimeDelta::nanoseconds(1_333_333_333));
+ }
+
+ #[test]
+ fn test_duration_sum() {
+ let duration_list_1 = [TimeDelta::zero(), TimeDelta::seconds(1)];
+ let sum_1: TimeDelta = duration_list_1.iter().sum();
+ assert_eq!(sum_1, TimeDelta::seconds(1));
+
+ let duration_list_2 = [
+ TimeDelta::zero(),
+ TimeDelta::seconds(1),
+ TimeDelta::seconds(6),
+ TimeDelta::seconds(10),
+ ];
+ let sum_2: TimeDelta = duration_list_2.iter().sum();
+ assert_eq!(sum_2, TimeDelta::seconds(17));
+
+ let duration_arr = [
+ TimeDelta::zero(),
+ TimeDelta::seconds(1),
+ TimeDelta::seconds(6),
+ TimeDelta::seconds(10),
+ ];
+ let sum_3: TimeDelta = duration_arr.into_iter().sum();
+ assert_eq!(sum_3, TimeDelta::seconds(17));
+ }
+
+ #[test]
+ fn test_duration_fmt() {
+ assert_eq!(TimeDelta::zero().to_string(), "P0D");
+ assert_eq!(TimeDelta::days(42).to_string(), "PT3628800S");
+ assert_eq!(TimeDelta::days(-42).to_string(), "-PT3628800S");
+ assert_eq!(TimeDelta::seconds(42).to_string(), "PT42S");
+ assert_eq!(TimeDelta::milliseconds(42).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::days(7) + TimeDelta::milliseconds(6543)).to_string(),
+ "PT604806.543S"
+ );
+ assert_eq!(TimeDelta::seconds(-86_401).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::days(1) + TimeDelta::milliseconds(2345)),
+ "PT86402.345S"
+ );
+ }
+
+ #[test]
+ fn test_to_std() {
+ assert_eq!(TimeDelta::seconds(1).to_std(), Ok(Duration::new(1, 0)));
+ assert_eq!(TimeDelta::seconds(86_401).to_std(), Ok(Duration::new(86_401, 0)));
+ assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(Duration::new(0, 123_000_000)));
+ assert_eq!(TimeDelta::milliseconds(123_765).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::seconds(-1).to_std(), Err(OutOfRangeError(())));
+ assert_eq!(TimeDelta::milliseconds(-1).to_std(), Err(OutOfRangeError(())));
+ }
+
+ #[test]
+ fn test_from_std() {
+ assert_eq!(Ok(TimeDelta::seconds(1)), TimeDelta::from_std(Duration::new(1, 0)));
+ assert_eq!(Ok(TimeDelta::seconds(86_401)), TimeDelta::from_std(Duration::new(86_401, 0)));
+ assert_eq!(
+ Ok(TimeDelta::milliseconds(123)),
+ TimeDelta::from_std(Duration::new(0, 123_000_000))
+ );
+ assert_eq!(
+ Ok(TimeDelta::milliseconds(123_765)),
+ 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 = TimeDelta::weeks(1);
+ const ONE_DAY: TimeDelta = TimeDelta::days(1);
+ const ONE_HOUR: TimeDelta = TimeDelta::hours(1);
+ const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1);
+ const ONE_SECOND: TimeDelta = TimeDelta::seconds(1);
+ const ONE_MILLI: TimeDelta = TimeDelta::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::seconds(86400 * 7 + 86400 + 3600 + 60 + 1)
+ + TimeDelta::nanoseconds(1 + 1_000 + 1_000_000)
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "rkyv-validation")]
+ fn test_rkyv_validation() {
+ let duration = TimeDelta::seconds(1);
+ let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap();
+ assert_eq!(rkyv::from_bytes::<TimeDelta>(&bytes).unwrap(), duration);
+ }
+}
diff --git a/src/traits.rs b/src/traits.rs
new file mode 100644
index 0000000..0018a7d
--- /dev/null
+++ b/src/traits.rs
@@ -0,0 +1,389 @@
+use crate::{IsoWeek, 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 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<Tz>`] if the resulting date and time fall within a timezone
+ /// transition such as from DST to standard time.
+ ///
+ /// [`NaiveDate`]: crate::NaiveDate
+ /// [`DateTime<Tz>`]: crate::DateTime
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, Datelike};
+ ///
+ /// 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<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use chrono::{NaiveDate, Datelike};
+ ///
+ /// 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::{NaiveDate, Datelike};
+ ///
+ /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
+ /// 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> {
+ /// 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<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ fn with_month0(&self, month0: u32) -> Option<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ fn with_day(&self, day: u32) -> Option<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ fn with_day0(&self, day0: u32) -> Option<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ fn with_ordinal(&self, ordinal: u32) -> Option<Self>;
+
+ /// 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<Tz>`] 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<Tz>`]: crate::DateTime
+ fn with_ordinal0(&self, ordinal0: u32) -> Option<Self>;
+
+ /// 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_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
+ }
+}
+
+/// 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<Self>;
+
+ /// 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<Self>;
+
+ /// 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<Self>;
+
+ /// 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<Self>;
+
+ /// 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::{NaiveDate, TimeDelta};
+
+ /// 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: Datelike>(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 + TimeDelta::days(133);
+ assert_eq!(
+ mid_year.num_days_from_ce(),
+ num_days_from_ce(&mid_year),
+ "on {:?}",
+ mid_year
+ );
+ }
+ }
+}
diff --git a/src/weekday.rs b/src/weekday.rs
new file mode 100644
index 0000000..618d535
--- /dev/null
+++ b/src/weekday.rs
@@ -0,0 +1,398 @@
+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::<Weekday>().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(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
+#[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 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.num_days_from(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.num_days_from(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_attr(not(feature = "clock"), doc = "```ignore")]
+ #[cfg_attr(feature = "clock", doc = "```rust")]
+ /// # 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.num_days_from(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.num_days_from(Weekday::Sun)
+ }
+
+ /// Returns a day-of-week number starting from the parameter `day` (D) = 0.
+ ///
+ /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6`
+ /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
+ /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6
+ #[inline]
+ pub(crate) const fn num_days_from(&self, day: Weekday) -> u32 {
+ (*self as u32 + 7 - day as u32) % 7
+ }
+}
+
+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",
+ })
+ }
+}
+
+/// 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<u8> for Weekday {
+ type Error = OutOfRange;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ 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<Weekday> {
+ 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<Weekday> {
+ 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(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 {{ .. }}")
+ }
+}
+
+// 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ 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<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ deserializer.deserialize_str(WeekdayVisitor)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Weekday;
+
+ #[test]
+ fn test_num_days_from() {
+ for i in 0..7 {
+ let base_day = Weekday::try_from(i).unwrap();
+
+ assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon));
+ assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun));
+
+ assert_eq!(base_day.num_days_from(base_day), 0);
+
+ assert_eq!(base_day.num_days_from(base_day.pred()), 1);
+ assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2);
+ assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3);
+ assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4);
+ assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5);
+ assert_eq!(
+ base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()),
+ 6
+ );
+
+ assert_eq!(base_day.num_days_from(base_day.succ()), 6);
+ assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5);
+ assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4);
+ assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3);
+ assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2);
+ assert_eq!(
+ base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()),
+ 1
+ );
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn test_serde_serialize() {
+ use 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]
+ #[cfg(feature = "serde")]
+ fn test_serde_deserialize() {
+ use 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::<Weekday>(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::<Weekday>(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::<Weekday>(&bytes).unwrap(), mon);
+ }
+}
diff --git a/taplo.toml b/taplo.toml
new file mode 100644
index 0000000..5fe42eb
--- /dev/null
+++ b/taplo.toml
@@ -0,0 +1,4 @@
+include = ["deny.toml", "**/Cargo.toml"]
+
+[formatting]
+inline_table_expand = false
diff --git a/tests/dateutils.rs b/tests/dateutils.rs
new file mode 100644
index 0000000..cf3d908
--- /dev/null
+++ b/tests/dateutils.rs
@@ -0,0 +1,162 @@
+#![cfg(all(unix, feature = "clock", feature = "std"))]
+
+use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike};
+use std::{path, process, thread};
+
+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 wheter the `date` output needs to
+ // be exactly matched, or whether LocalResult::Ambigious 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::LocalResult::Ambiguous(a, b) => assert!(
+ format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str
+ ),
+ chrono::LocalResult::Single(a) => {
+ assert_eq!(format!("{}\n", a), date_command_str);
+ }
+ chrono::LocalResult::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: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr);
+ assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH);
+}
+
+#[test]
+fn try_verify_against_date_command() {
+ if !path::Path::new(DATE_PATH).exists() {
+ eprintln!("date command {:?} not found, skipping", DATE_PATH);
+ return;
+ }
+ assert_run_date_version();
+
+ eprintln!(
+ "Run command {:?} for every hour from 1975 to 2077, skipping some years...",
+ DATE_PATH,
+ );
+
+ 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::hours(1);
+ }
+ }));
+ }
+ 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 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 formated_date = format!("{}\n", ldt.format(required_format));
+ assert_eq!(date_command_str, formated_date);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn try_verify_against_date_command_format() {
+ if !path::Path::new(DATE_PATH).exists() {
+ eprintln!("date command {:?} not found, skipping", DATE_PATH);
+ 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 += chrono::TimeDelta::days(55);
+ }
+}
diff --git a/tests/wasm.rs b/tests/wasm.rs
index 275d120..6937da9 100644
--- a/tests/wasm.rs
+++ b/tests/wasm.rs
@@ -1,67 +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_arch = "wasm32",
+ feature = "wasmbind",
+ feature = "clock",
+ not(any(target_os = "emscripten", target_os = "wasi"))
+))]
- #[wasm_bindgen_test]
- fn now() {
- let utc: DateTime<Utc> = Utc::now();
- let local: DateTime<Local> = 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> = Utc::now();
+ let local: DateTime<Local> = 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::minutes(5),
+ "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::<Utc>::from(now.clone());
- let dt = DateTime::<Utc>::from(now.clone());
+ 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);
+}
- assert_eq!(now.get_time() as i64, dt.timestamp_millis());
- }
+#[wasm_bindgen_test]
+fn convert_all_parts_with_milliseconds() {
+ let time: DateTime<Utc> = "2020-12-01T03:01:55.974Z".parse().unwrap();
+ let js_date = js_sys::Date::from(time);
- #[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);
- }
+ 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 0000000..bfb110c
--- /dev/null
+++ b/tests/win_bindings.rs
@@ -0,0 +1,22 @@
+#![cfg(all(windows, feature = "clock", feature = "std"))]
+
+use std::fs;
+use windows_bindgen::bindgen;
+
+#[test]
+fn gen_bindings() {
+ let input = "src/offset/local/win_bindings.txt";
+ let output = "src/offset/local/win_bindings.rs";
+ let existing = fs::read_to_string(output).unwrap();
+
+ let log = bindgen(["--etc", input]).unwrap();
+ eprintln!("{}", log);
+
+ // 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 new = fs::read_to_string(output).unwrap();
+ if !new.lines().eq(existing.lines()) {
+ panic!("generated file `{}` is changed.", output);
+ }
+}