chrono-0.4.31/.cargo_vcs_info.json0000644000000001360000000000100124110ustar { "git": { "sha1": "e730c6ac45649a6a636abf30b796304bc46ecd15" }, "path_in_vcs": "" }chrono-0.4.31/.git-ignore-revs000064400000000000000000000000660072674642500142550ustar 00000000000000febb8dc168325ac471b54591c925c48b6a485962 # cargo fmt chrono-0.4.31/.github/codecov.yml000064400000000000000000000001060072674642500147330ustar 00000000000000coverage: status: project: default: threshold: 1% chrono-0.4.31/.github/dependabot.yml000064400000000000000000000005760072674642500154310ustar 00000000000000version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" target-branch: "0.4.x" - package-ecosystem: "cargo" directory: "/fuzz/" schedule: interval: "weekly" target-branch: "0.4.x" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" target-branch: "0.4.x" chrono-0.4.31/.github/pull_request_template.md000064400000000000000000000004140072674642500175310ustar 00000000000000### Thanks for contributing to chrono! If your feature is semver-compatible, please target the 0.4.x branch; the main branch will be used for 0.5.0 development going forward. Please consider adding a test to ensure your bug fix/feature will not break in the future. chrono-0.4.31/.github/workflows/codecov.yml000064400000000000000000000015700072674642500167760ustar 00000000000000name: codecov 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 --all-features --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: lcov.info fail_ci_if_error: true chrono-0.4.31/.github/workflows/lint.yml000064400000000000000000000033430072674642500163220ustar 00000000000000name: lint on: push: branches: [main, 0.4.x] pull_request: jobs: lint: runs-on: ubuntu-latest steps: - 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 --all-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: - 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: RUSTFLAGS="--cfg docsrs" cargo deadlinks -- --features=serde - run: cargo doc --all-features --no-deps env: RUSTDOCFLAGS: -Dwarnings cffconvert: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: citation-file-format/cffconvert-github-action@2.0.0 with: args: --validate chrono-0.4.31/.github/workflows/test.yml000064400000000000000000000144610072674642500163360ustar 00000000000000name: All Tests and Builds on: push: branches: [main, 0.4.x] pull_request: jobs: timezones: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test --all-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.57 # * 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.57 - 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 }} - 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 --all-features --color=always -- --color=always - run: cargo test --doc --all-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 serde,rkyv \ --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks shell: bash env: 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 no_std: strategy: matrix: os: [ubuntu-latest] target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test alternative_targets: strategy: matrix: os: [ubuntu-latest] target: [ wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android, ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: node-version: "12" - run: cargo build --target ${{ matrix.target }} --color=always test_wasm: strategy: matrix: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v3 - 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@v4 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - 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 --all-features --target i686-unknown-linux-gnu --color=always - run: cross test --doc --all-features --target i686-unknown-linux-gnu --color=always - run: cross test --lib --all-features --target i686-unknown-linux-musl --color=always - run: cross test --doc --all-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 --all-features --no-deps env: RUSTDOCFLAGS: "-D warnings --cfg docsrs" chrono-0.4.31/.gitignore000064400000000000000000000000770072674642500132250ustar 00000000000000target Cargo.lock .tool-versions # for jetbrains users .idea/ chrono-0.4.31/CHANGELOG.md000064400000000000000000000566510072674642500130570ustar 00000000000000ChangeLog for Chrono ==================== 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 * Correct build on solaris/illumos ## 0.4.18 * Restore support for x86_64-fortanix-unknown-sgx ## 0.4.17 * Fix a name resolution error in wasm-bindgen code introduced by removing the dependency on time v0.1 ## 0.4.16 ### Features * Add %Z specifier to the `FromStr`, similar to the glibc strptime (does not set the offset from the timezone name) * Drop the dependency on time v0.1, which is deprecated, unless the `oldtime` feature is active. This feature is active by default in v0.4.16 for backwards compatibility, but will likely be removed in v0.5. Code that imports `time::Duration` should be switched to import `chrono::Duration` instead to avoid breakage. ## 0.4.15 ### Fixes * Correct usage of vec in specific feature combinations (@quodlibetor) ## 0.4.14 **YANKED** ### Features * Add day and week iterators for `NaiveDate` (@gnzlbg & @robyoung) * Add a `Month` enum (@hhamana) * Add `locales`. All format functions can now use locales, see the documentation for the `unstable-locales` feature. * Fix `Local.from_local_datetime` method for wasm ### Improvements * Added MIN and MAX values for `NaiveTime`, `NaiveDateTime` and `DateTime`. ## 0.4.13 ### Features * Add `DurationRound` trait that allows rounding and truncating by `Duration` (@robyoung) ### Internal Improvements * Code improvements to impl `From` for `js_sys` in wasm to reuse code (@schrieveslaach) ## 0.4.12 ### New Methods and impls * `Duration::abs` to ensure that a duration is just a magnitude (#418 @abreis). ### Compatibility improvements * impl `From` for `js_sys` in wasm (#424 @schrieveslaach) * Bump required version of `time` for redox support. ### Bugfixes * serde modules do a better job with `Option` types (#417 @mwkroening and #429 @fx-kirin) * Use js runtime when using wasmbind to get the local offset (#412 @quodlibetor) ### Internal Improvements * Migrate to github actions from travis-ci, make the overall CI experience more comprehensible, significantly faster and more correct (#439 @quodlibetor) ## 0.4.11 ### Improvements * Support a space or `T` in `FromStr` for `DateTime`, meaning that e.g. `dt.to_string().parse::>()` now correctly works on round-trip. (@quodlibetor in #378) * Support "negative UTC" in `parse_from_rfc2822` (@quodlibetor #368 reported in #102) * Support comparisons of DateTimes with different timezones (@dlalic in #375) * Many documentation improvements ### Bitrot and external integration fixes * Don't use wasmbind on wasi (@coolreader18 #365) * Avoid deprecation warnings for `Error::description` (@AnderEnder and @quodlibetor #376) ### Internal improvements * Use Criterion for benchmarks (@quodlibetor) ## 0.4.10 ### Compatibility notes * Putting some functionality behind an `alloc` feature to improve no-std support (in #341) means that if you were relying on chrono with `no-default-features` *and* using any of the functions that require alloc support (i.e. any of the string-generating functions like `to_rfc3339`) you will need to add the `alloc` feature in your Cargo.toml. ### Improvements * `DateTime::parse_from_str` is more than 2x faster in some cases. (@michalsrb #358) * Significant improvements to no-std and alloc support (This should also make many format/serialization operations induce zero unnecessary allocations) (@CryZe #341) ### Features * Functions that were accepting `Iterator` of `Item`s (for example `format_with_items`) now accept `Iterator` of `Borrow`, so one can use values or references. (@michalsrb #358) * Add built-in support for structs with nested `Option` etc fields (@manifest #302) ### Internal/doc improvements * Use markdown footnotes on the `strftime` docs page (@qudlibetor #359) * Migrate from `try!` -> `?` (question mark) because it is now emitting deprecation warnings and has been stable since rustc 1.13.0 * Deny dead code ## 0.4.9 ### Fixes * Make Datetime arithmatic adjust their offsets after discovering their new timestamps (@quodlibetor #337) * Put wasm-bindgen related code and dependencies behind a `wasmbind` feature gate. (@quodlibetor #335) ## 0.4.8 ### Fixes * Add '0' to single-digit days in rfc2822 date format (@wyhaya #323) * Correctly pad DelayedFormat (@SamokhinIlya #320) ### Features * Support `wasm-unknown-unknown` via wasm-bindgen (in addition to emscripten/`wasm-unknown-emscripten`). (finished by @evq in #331, initial work by @jjpe #287) ## 0.4.7 ### Fixes * Disable libc default features so that CI continues to work on rust 1.13 * Fix panic on negative inputs to timestamp_millis (@cmars #292) * Make `LocalResult` `Copy/Eq/Hash` ### Features * Add `std::convert::From` conversions between the different timezone formats (@mqudsi #271) * Add `timestamp_nanos` methods (@jean-airoldie #308) * Documentation improvements ## 0.4.6 ### Maintenance * Doc improvements -- improve README CI verification, external links * winapi upgrade to 0.3 ## Unreleased ### Features * Added `NaiveDate::from_weekday_of_month{,_opt}` for getting eg. the 2nd Friday of March 2017. ## 0.4.5 ### Features * Added several more serde deserialization helpers (@novacrazy #258) * Enabled all features on the playground (@davidtwco #267) * Derive `Hash` on `FixedOffset` (@LuoZijun #254) * Improved docs (@storyfeet #261, @quodlibetor #252) ## 0.4.4 ### Features * Added support for parsing nanoseconds without the leading dot (@emschwartz #251) ## 0.4.3 ### Features * Added methods to DateTime/NaiveDateTime to present the stored value as a number of nanoseconds since the UNIX epoch (@harkonenbade #247) * Added a serde serialise/deserialise module for nanosecond timestamps. (@harkonenbade #247) * Added "Permissive" timezone parsing which allows a numeric timezone to be specified without minutes. (@quodlibetor #242) ## 0.4.2 ### Deprecations * More strongly deprecate RustcSerialize: remove it from documentation unless the feature is enabled, issue a deprecation warning if the rustc-serialize feature is enabled (@quodlibetor #174) ### Features * Move all uses of the system clock behind a `clock` feature, for use in environments where we don't have access to the current time. (@jethrogb #236) * Implement subtraction of two `Date`s, `Time`s, or `DateTime`s, returning a `Duration` (@tobz1000 #237) ## 0.4.1 ### Bug Fixes * Allow parsing timestamps with subsecond precision (@jonasbb) * RFC2822 allows times to not include the second (@upsuper) ### Features * New `timestamp_millis` method on `DateTime` and `NaiveDateTim` that returns number of milliseconds since the epoch. (@quodlibetor) * Support exact decimal width on subsecond display for RFC3339 via a new `to_rfc3339_opts` method on `DateTime` (@dekellum) * Use no_std-compatible num dependencies (@cuviper) * Add `SubsecRound` trait that allows rounding to the nearest second (@dekellum) ### Code Hygiene and Docs * Docs! (@alatiera @kosta @quodlibetor @kennytm) * Run clippy and various fixes (@quodlibetor) ## 0.4.0 (2017-06-22) This was originally planned as a minor release but was pushed to a major release due to the compatibility concern raised. ### Added - `IsoWeek` has been added for the ISO week without time zone. - The `+=` and `-=` operators against `time::Duration` are now supported for `NaiveDate`, `NaiveTime` and `NaiveDateTime`. (#99) (Note that this does not invalidate the eventual deprecation of `time::Duration`.) - `SystemTime` and `DateTime` types can be now converted to each other via `From`. Due to the obvious lack of time zone information in `SystemTime`, the forward direction is limited to `DateTime` and `DateTime` only. ### Changed - Intermediate implementation modules have been flattened (#161), and `UTC` has been renamed to `Utc` in accordance with the current convention (#148). The full list of changes is as follows: Before | After ---------------------------------------- | ---------------------------- `chrono::date::Date` | `chrono::Date` `chrono::date::MIN` | `chrono::MIN_DATE` `chrono::date::MAX` | `chrono::MAX_DATE` `chrono::datetime::DateTime` | `chrono::DateTime` `chrono::naive::time::NaiveTime` | `chrono::naive::NaiveTime` `chrono::naive::date::NaiveDate` | `chrono::naive::NaiveDate` `chrono::naive::date::MIN` | `chrono::naive::MIN_DATE` `chrono::naive::date::MAX` | `chrono::naive::MAX_DATE` `chrono::naive::datetime::NaiveDateTime` | `chrono::naive::NaiveDateTime` `chrono::offset::utc::UTC` | `chrono::offset::Utc` `chrono::offset::fixed::FixedOffset` | `chrono::offset::FixedOffset` `chrono::offset::local::Local` | `chrono::offset::Local` `chrono::format::parsed::Parsed` | `chrono::format::Parsed` With an exception of `Utc`, this change does not affect any direct usage of `chrono::*` or `chrono::prelude::*` types. - `Datelike::isoweekdate` is replaced by `Datelike::iso_week` which only returns the ISO week. The original method used to return a tuple of year number, week number and day of the week, but this duplicated the `Datelike::weekday` method and it had been hard to deal with the raw year and week number for the ISO week date. This change isolates any logic and API for the week date into a separate type. - `NaiveDateTime` and `DateTime` can now be deserialized from an integral UNIX timestamp. (#125) This turns out to be very common input for web-related usages. The existing string representation is still supported as well. - `chrono::serde` and `chrono::naive::serde` modules have been added for the serialization utilities. (#125) Currently they contain the `ts_seconds` modules that can be used to serialize `NaiveDateTime` and `DateTime` values into an integral UNIX timestamp. This can be combined with Serde's `[de]serialize_with` attributes to fully support the (de)serialization to/from the timestamp. For rustc-serialize, there are separate `chrono::TsSeconds` and `chrono::naive::TsSeconds` types that are newtype wrappers implementing different (de)serialization logics. This is a suboptimal API, however, and it is strongly recommended to migrate to Serde. ### Fixed - The major version was made to fix the broken Serde dependency issues. (#146, #156, #158, #159) The original intention to technically break the dependency was to facilitate the use of Serde 1.0 at the expense of temporary breakage. Whether this was appropriate or not is quite debatable, but it became clear that there are several high-profile crates requiring Serde 0.9 and it is not feasible to force them to use Serde 1.0 anyway. To the end, the new major release was made with some known lower-priority breaking changes. 0.3.1 is now yanked and any remaining 0.3 users can safely roll back to 0.3.0. - Various documentation fixes and goodies. (#92, #131, #136) ## 0.3.1 (2017-05-02) ### Added - `Weekday` now implements `FromStr`, `Serialize` and `Deserialize`. (#113) The syntax is identical to `%A`, i.e. either the shortest or the longest form of English names. ### Changed - Serde 1.0 is now supported. (#142) This is technically a breaking change because Serde 0.9 and 1.0 are not compatible, but this time we decided not to issue a minor version because we have already seen Serde 0.8 and 0.9 compatibility problems even after 0.3.0 and a new minor version turned out to be not very helpful for this kind of issues. ### Fixed - Fixed a bug that the leap second can be mapped wrongly in the local time zone. Only occurs when the local time zone is behind UTC. (#130) ## 0.3.0 (2017-02-07) The project has moved to the [Chronotope](https://github.com/chronotope/) organization. ### Added - `chrono::prelude` module has been added. All other glob imports are now discouraged. - `FixedOffset` can be added to or subtracted from any timelike types. - `FixedOffset::local_minus_utc` and `FixedOffset::utc_minus_local` methods have been added. Note that the old `Offset::local_minus_utc` method is gone; see below. - Serde support for non-self-describing formats like Bincode is added. (#89) - Added `Item::Owned{Literal,Space}` variants for owned formatting items. (#76) - Formatting items and the `Parsed` type have been slightly adjusted so that they can be internally extended without breaking any compatibility. - `Weekday` is now `Hash`able. (#109) - `ParseError` now implements `Eq` as well as `PartialEq`. (#114) - More documentation improvements. (#101, #108, #112) ### Changed - Chrono now only supports Rust 1.13.0 or later (previously: Rust 1.8.0 or later). - Serde 0.9 is now supported. Due to the API difference, support for 0.8 or older is discontinued. (#122) - Rustc-serialize implementations are now on par with corresponding Serde implementations. They both standardize on the `std::fmt::Debug` textual output. **This is a silent breaking change (hopefully the last though).** You should be prepared for the format change if you depended on rustc-serialize. - `Offset::local_minus_utc` is now `Offset::fix`, and returns `FixedOffset` instead of a duration. This makes every time zone operation operate within a bias less than one day, and vastly simplifies many logics. - `chrono::format::format` now receives `FixedOffset` instead of `time::Duration`. - The following methods and implementations have been renamed and older names have been *removed*. The older names will be reused for the same methods with `std::time::Duration` in the future. - `checked_*` → `checked_*_signed` in `Date`, `DateTime`, `NaiveDate` and `NaiveDateTime` types - `overflowing_*` → `overflowing_*_signed` in the `NaiveTime` type - All subtraction implementations between two time instants have been moved to `signed_duration_since`, following the naming in `std::time`. ### Fixed - Fixed a panic when the `Local` offset receives a leap second. (#123) ### Removed - Rustc-serialize support for `Date` types and all offset types has been dropped. These implementations were automatically derived and never had been in a good shape. Moreover there are no corresponding Serde implementations, limiting their usefulness. In the future they may be revived with more complete implementations. - The following method aliases deprecated in the 0.2 branch have been removed. - `DateTime::num_seconds_from_unix_epoch` (→ `DateTime::timestamp`) - `NaiveDateTime::from_num_seconds_from_unix_epoch` (→ `NaiveDateTime::from_timestamp`) - `NaiveDateTime::from_num_seconds_from_unix_epoch_opt` (→ `NaiveDateTime::from_timestamp_opt`) - `NaiveDateTime::num_seconds_unix_epoch` (→ `NaiveDateTime::timestamp`) - Formatting items are no longer `Copy`, except for `chrono::format::Pad`. - `chrono::offset::add_with_leapsecond` has been removed. Use a direct addition with `FixedOffset` instead. ## 0.2.25 (2016-08-04) This is the last version officially supports Rust 1.12.0 or older. (0.2.24 was accidentally uploaded without a proper check for warnings in the default state, and replaced by 0.2.25 very shortly. Duh.) ### Added - Serde 0.8 is now supported. 0.7 also remains supported. (#86) ### Fixed - The deserialization implementation for rustc-serialize now properly verifies the input. All serialization codes are also now thoroughly tested. (#42) ## 0.2.23 (2016-08-03) ### Added - The documentation was greatly improved for several types, and tons of cross-references have been added. (#77, #78, #80, #82) - `DateTime::timestamp_subsec_{millis,micros,nanos}` methods have been added. (#81) ### Fixed - When the system time records a leap second, the nanosecond component was mistakenly reset to zero. (#84) - `Local` offset misbehaves in Windows for August and later, due to the long-standing libtime bug (dates back to mid-2015). Workaround has been implemented. (#85) ## 0.2.22 (2016-04-22) ### Fixed - `%.6f` and `%.9f` used to print only three digits when the nanosecond part is zero. (#71) - The documentation for `%+` has been updated to reflect the current status. (#71) ## 0.2.21 (2016-03-29) ### Fixed - `Fixed::LongWeekdayName` was unable to recognize `"sunday"` (whoops). (#66) ## 0.2.20 (2016-03-06) ### Changed - `serde` dependency has been updated to 0.7. (#63, #64) ## 0.2.19 (2016-02-05) ### Added - The documentation for `Date` is made clear about its ambiguity and guarantees. ### Fixed - `DateTime::date` had been wrong when the local date and the UTC date is in disagreement. (#61) ## 0.2.18 (2016-01-23) ### Fixed - Chrono no longer pulls a superfluous `rand` dependency. (#57) ## 0.2.17 (2015-11-22) ### Added - Naive date and time types and `DateTime` now have a `serde` support. They serialize as an ISO 8601 / RFC 3339 string just like `Debug`. (#51) ## 0.2.16 (2015-09-06) ### Added - Added `%.3f`, `%.6f` and `%.9f` specifier for formatting fractional seconds up to 3, 6 or 9 decimal digits. This is a natural extension to the existing `%f`. Note that this is (not yet) generic, no other value of precision is supported. (#45) ### Changed - Forbade unsized types from implementing `Datelike` and `Timelike`. This does not make a big harm as any type implementing them should be already sized to be practical, but this change still can break highly generic codes. (#46) ### Fixed - Fixed a broken link in the `README.md`. (#41) ## 0.2.15 (2015-07-05) ### Added - Padding modifiers `%_?`, `%-?` and `%0?` are implemented. They are glibc extensions which seem to be reasonably widespread (e.g. Ruby). - Added `%:z` specifier and corresponding formatting items which is essentially the same as `%z` but with a colon. - Added a new specifier `%.f` which precision adapts from the input. This was added as a response to the UX problems in the original nanosecond specifier `%f`. ### Fixed - `Numeric::Timestamp` specifier (`%s`) was ignoring the time zone offset when provided. - Improved the documentation and associated tests for `strftime`. ## 0.2.14 (2015-05-15) ### Fixed - `NaiveDateTime +/- Duration` or `NaiveTime +/- Duration` could have gone wrong when the `Duration` to be added is negative and has a fractional second part. This was caused by an underflow in the conversion from `Duration` to the parts; the lack of tests for this case allowed a bug. (#37) ## 0.2.13 (2015-04-29) ### Added - The optional dependency on `rustc_serialize` and relevant `Rustc{En,De}codable` implementations for supported types has been added. This is enabled by the `rustc-serialize` Cargo feature. (#34) ### Changed - `chrono::Duration` reexport is changed to that of crates.io `time` crate. This enables Rust 1.0 beta compatibility. ## 0.2.4 (2015-03-03) ### Fixed - Clarified the meaning of `Date` and fixed unwanted conversion problem that only occurs with positive UTC offsets. (#27) ## 0.2.3 (2015-02-27) ### Added - `DateTime` and `Date` is now `Copy`/`Send` when `Tz::Offset` is `Copy`/`Send`. The implementations for them were mistakenly omitted. (#25) ### Fixed - `Local::from_utc_datetime` didn't set a correct offset. (#26) ## 0.2.1 (2015-02-21) ### Changed - `DelayedFormat` no longer conveys a redundant lifetime. ## 0.2.0 (2015-02-19) ### Added - `Offset` is splitted into `TimeZone` (constructor) and `Offset` (storage) types. You would normally see only the former, as the latter is mostly an implementation detail. Most importantly, `Local` now can be used to directly construct timezone-aware values. Some types (currently, `UTC` and `FixedOffset`) are both `TimeZone` and `Offset`, but others aren't (e.g. `Local` is not what is being stored to each `DateTime` values). - `LocalResult::map` convenience method has been added. - `TimeZone` now allows a construction of `DateTime` values from UNIX timestamp, via `timestamp` and `timestamp_opt` methods. - `TimeZone` now also has a method for parsing `DateTime`, namely `datetime_from_str`. - The following methods have been added to all date and time types: - `checked_add` - `checked_sub` - `format_with_items` - The following methods have been added to all timezone-aware types: - `timezone` - `with_timezone` - `naive_utc` - `naive_local` - `parse_from_str` method has been added to all naive types and `DateTime`. - All naive types and instances of `DateTime` with time zones `UTC`, `Local` and `FixedOffset` implement the `FromStr` trait. They parse what `std::fmt::Debug` would print. - `chrono::format` has been greatly rewritten. - The formatting syntax parser is modular now, available at `chrono::format::strftime`. - The parser and resolution algorithm is also modular, the former is available at `chrono::format::parse` while the latter is available at `chrono::format::parsed`. - Explicit support for RFC 2822 and 3339 syntaxes is landed. - There is a minor formatting difference with atypical values, e.g. for years not between 1 BCE and 9999 CE. ### Changed - Most uses of `Offset` are converted to `TimeZone`. In fact, *all* user-facing code is expected to be `Offset`-free. - `[Naive]DateTime::*num_seconds_from_unix_epoch*` methods have been renamed to simply `timestamp` or `from_timestamp*`. The original names have been deprecated. ### Removed - `Time` has been removed. This also prompts a related set of methods in `TimeZone`. This is in principle possible, but in practice has seen a little use because it can only be meaningfully constructed via an existing `DateTime` value. This made many operations to `Time` unintuitive or ambiguous, so we simply let it go. In the case that `Time` is really required, one can use a simpler `NaiveTime`. `NaiveTime` and `NaiveDate` can be freely combined and splitted, and `TimeZone::from_{local,utc}_datetime` can be used to convert from/to the local time. - `with_offset` method has been removed. Use `with_timezone` method instead. (This is not deprecated since it is an integral part of offset reform.) ## 0.1.14 (2015-01-10) ### Added - Added a missing `std::fmt::String` impl for `Local`. ## 0.1.13 (2015-01-10) ### Changed - Most types now implement both `std::fmt::Show` and `std::fmt::String`, with the former used for the stricter output and the latter used for more casual output. ### Removed - `Offset::name` has been replaced by a `std::fmt::String` implementation to `Offset`. ## 0.1.12 (2015-01-08) ### Removed - `Duration + T` no longer works due to the updated impl reachability rules. Use `T + Duration` as a workaround. ## 0.1.4 (2014-12-13) ### Fixed - Fixed a bug that `Date::and_*` methods with an offset that can change the date are off by one day. ## 0.1.3 (2014-11-28) ### Added - `{Date,Time,DateTime}::with_offset` methods have been added. - `LocalResult` now implements a common set of traits. - `LocalResult::and_*` methods have been added. They are useful for safely chaining `LocalResult>` methods to make `LocalResult>`. ### Changed - `Offset::name` now returns `SendStr`. - `{Date,Time} - Duration` overloadings are now allowed. ## 0.1.2 (2014-11-24) ### Added - `Duration + Date` overloading is now allowed. ### Changed - Chrono no longer needs `num` dependency. ## 0.1.0 (2014-11-20) The initial version that was available to `crates.io`. chrono-0.4.31/CITATION.cff000064400000000000000000000014000072674642500131160ustar 00000000000000# Parser settings. cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. date-released: 2023-09-15 version: 0.4.31 # 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 chrono-0.4.31/Cargo.toml0000644000000054720000000000100104170ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # 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. # # 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.57.0" name = "chrono" version = "0.4.31" 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", ] categories = ["date-and-time"] license = "MIT OR Apache-2.0" repository = "https://github.com/chronotope/chrono" [package.metadata.docs.rs] features = ["serde"] rustdoc-args = [ "--cfg", "docsrs", ] [package.metadata.playground] features = ["serde"] [lib] name = "chrono" [dependencies.arbitrary] version = "1.0.0" features = ["derive"] optional = true [dependencies.num-traits] version = "0.2" default-features = false [dependencies.pure-rust-locales] version = "0.7" optional = true [dependencies.rkyv] version = "0.7" optional = true [dependencies.rustc-serialize] version = "0.3.20" optional = true [dependencies.serde] version = "1.0.99" optional = true default-features = false [dev-dependencies.bincode] version = "1.3.0" [dev-dependencies.serde_derive] version = "1" default-features = false [dev-dependencies.serde_json] version = "1" [features] __internal_bench = [] alloc = [] clock = [ "std", "winapi", "iana-time-zone", "android-tzdata", ] default = [ "clock", "std", "oldtime", "wasmbind", ] libc = [] oldtime = [] std = [] unstable-locales = [ "pure-rust-locales", "alloc", ] 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 [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(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.48" optional = true [target."cfg(windows)".dev-dependencies.windows-bindgen] version = "0.51" chrono-0.4.31/Cargo.toml.orig000064400000000000000000000042470072674642500141270ustar 00000000000000[package] name = "chrono" version = "0.4.31" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" repository = "https://github.com/chronotope/chrono" keywords = ["date", "time", "calendar"] categories = ["date-and-time"] readme = "README.md" license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2021" rust-version = "1.57.0" [lib] name = "chrono" [features] default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] winapi = ["windows-targets"] std = [] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] __internal_bench = [] [dependencies] 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.7", optional = true } rkyv = { version = "0.7", optional = true } 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 [target.'cfg(windows)'.dependencies] windows-targets = { version = "0.48", optional = true } [target.'cfg(windows)'.dev-dependencies] windows-bindgen = { version = "0.51" } [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 = "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"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] features = ["serde"] chrono-0.4.31/LICENSE.txt000064400000000000000000000300230072674642500130520ustar 00000000000000Rust-chrono is dual-licensed under The MIT License [1] and Apache 2.0 License [2]. Copyright (c) 2014--2017, Kang Seonghoon and contributors. Nota Bene: This is same as the Rust Project's own license. [1]: , which is reproduced below: ~~~~ The MIT License (MIT) Copyright (c) 2014, Kang Seonghoon. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ~~~~ [2]: , which is reproduced below: ~~~~ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ~~~~ chrono-0.4.31/README.md000064400000000000000000000074060072674642500125170ustar 00000000000000[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] [![Chat][discord-image]][discord] [![codecov.io][codecov-img]][codecov-link] [gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg [gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest [cratesio-image]: https://img.shields.io/crates/v/chrono.svg [cratesio]: https://crates.io/crates/chrono [docsrs-image]: https://docs.rs/chrono/badge.svg [docsrs]: https://docs.rs/chrono [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 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): * 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. 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. ## Documentation See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference. ## Limitations * 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). ## 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`) and local timezone (`Local`). * `wasmbind`: Interface with the JS Date API for the `wasm32` target. Optional features: * `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. ## Rust version requirements The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done lightly. ## License This project is licensed under either of * [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) * [MIT License](https://opensource.org/licenses/MIT) at your option. chrono-0.4.31/appveyor.yml000064400000000000000000000003130072674642500136160ustar 00000000000000# 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" chrono-0.4.31/deny.toml000064400000000000000000000005420072674642500130660ustar 00000000000000[licenses] allow-osi-fsf-free = "either" copyleft = "deny" [advisories] ignore = [ "RUSTSEC-2020-0071", # time 0.1, doesn't affect the API we use "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility ] unmaintained = "deny" unsound = "deny" yanked = "deny" chrono-0.4.31/rustfmt.toml000064400000000000000000000000350072674642500136300ustar 00000000000000use_small_heuristics = "Max" chrono-0.4.31/src/date.rs000064400000000000000000000532150072674642500133110ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date with time zone. #![allow(deprecated)] #[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; #[cfg(feature = "unstable-locales")] use crate::format::Locale; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; use crate::DateTime; use crate::{Datelike, Weekday}; /// ISO 8601 calendar date with time zone. /// /// 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`: /// /// - 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`](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_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. #[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime` instead")] #[derive(Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Date { date: NaiveDate, offset: Tz::Offset, } /// The minimum possible `Date`. #[allow(deprecated)] #[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")] pub const MIN_DATE: Date = Date::::MIN_UTC; /// The maximum possible `Date`. #[allow(deprecated)] #[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] pub const MAX_DATE: Date = Date::::MAX_UTC; impl Date { /// Makes a new `Date` with given *UTC* date and offset. /// The local date should be constructed via the `TimeZone` trait. #[inline] #[must_use] pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date { Date { date, offset } } /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// /// Returns `None` on invalid datetime. #[inline] #[must_use] pub fn and_time(&self, time: NaiveTime) -> Option> { let localdt = self.naive_local().and_time(time); self.timezone().from_local_datetime(&localdt).single() } /// Makes a new `DateTime` from the current date, hour, minute and second. /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")] #[inline] #[must_use] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") } /// Makes a new `DateTime` from the current date, hour, minute and second. /// The offset in the current date is preserved. /// /// Returns `None` on invalid hour, minute and/or second. #[inline] #[must_use] pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option> { NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time)) } /// Makes a new `DateTime` from the current date, hour, minute, second and millisecond. /// The millisecond part can exceed 1,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")] #[inline] #[must_use] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } /// Makes a new `DateTime` from the current date, hour, minute, second and millisecond. /// The millisecond part can exceed 1,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Returns `None` on invalid hour, minute, second and/or millisecond. #[inline] #[must_use] pub fn and_hms_milli_opt( &self, hour: u32, min: u32, sec: u32, milli: u32, ) -> Option> { NaiveTime::from_hms_milli_opt(hour, min, sec, milli).and_then(|time| self.and_time(time)) } /// Makes a new `DateTime` from the current date, hour, minute, second and microsecond. /// The microsecond part can exceed 1,000,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or microsecond. #[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")] #[inline] #[must_use] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } /// Makes a new `DateTime` from the current date, hour, minute, second and microsecond. /// The microsecond part can exceed 1,000,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Returns `None` on invalid hour, minute, second and/or microsecond. #[inline] #[must_use] pub fn and_hms_micro_opt( &self, hour: u32, min: u32, sec: u32, micro: u32, ) -> Option> { NaiveTime::from_hms_micro_opt(hour, min, sec, micro).and_then(|time| self.and_time(time)) } /// Makes a new `DateTime` 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. /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")] #[inline] #[must_use] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } /// Makes a new `DateTime` 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. /// The offset in the current date is preserved. /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. #[inline] #[must_use] pub fn and_hms_nano_opt( &self, hour: u32, min: u32, sec: u32, nano: u32, ) -> Option> { NaiveTime::from_hms_nano_opt(hour, min, sec, nano).and_then(|time| self.and_time(time)) } /// Makes a new `Date` for the next date. /// /// Panics when `self` is the last representable date. #[deprecated(since = "0.4.23", note = "Use succ_opt() instead")] #[inline] #[must_use] pub fn succ(&self) -> Date { self.succ_opt().expect("out of bound") } /// Makes a new `Date` for the next date. /// /// Returns `None` when `self` is the last representable date. #[inline] #[must_use] pub fn succ_opt(&self) -> Option> { self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone())) } /// Makes a new `Date` for the prior date. /// /// Panics when `self` is the first representable date. #[deprecated(since = "0.4.23", note = "Use pred_opt() instead")] #[inline] #[must_use] pub fn pred(&self) -> Date { self.pred_opt().expect("out of bound") } /// Makes a new `Date` for the prior date. /// /// Returns `None` when `self` is the first representable date. #[inline] #[must_use] pub fn pred_opt(&self) -> Option> { self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) } /// Retrieves an associated offset from UTC. #[inline] #[must_use] pub fn offset(&self) -> &Tz::Offset { &self.offset } /// Retrieves an associated time zone. #[inline] #[must_use] pub fn timezone(&self) -> Tz { TimeZone::from_offset(&self.offset) } /// Changes the associated time zone. /// This does not change the actual `Date` (but will change the string representation). #[inline] #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> Date { tz.from_utc_date(&self.date) } /// Adds given `Duration` to the current date. /// /// Returns `None` when it will result in overflow. #[inline] #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { let date = self.date.checked_add_signed(rhs)?; Some(Date { date, offset: self.offset }) } /// Subtracts given `Duration` from the current date. /// /// Returns `None` when it will result in overflow. #[inline] #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { let date = self.date.checked_sub_signed(rhs)?; Some(Date { date, offset: self.offset }) } /// Subtracts another `Date` from the current date. /// Returns a `Duration` of integral numbers. /// /// This does not overflow or underflow at all, /// as all possible output fits in the range of `Duration`. #[inline] #[must_use] pub fn signed_duration_since(self, rhs: Date) -> OldDuration { 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 } /// Returns a view to the naive local date. /// /// This is technically the same as [`naive_utc`](#method.naive_utc) /// because the offset is restricted to never exceed one day, /// but provided for the consistency. #[inline] #[must_use] pub fn naive_local(&self) -> NaiveDate { self.date } /// Returns the number of whole years from the given `base` until `self`. #[must_use] pub fn years_since(&self, base: Self) -> Option { self.date.years_since(base.date) } /// The minimum possible `Date`. pub const MIN_UTC: Date = Date { date: NaiveDate::MIN, offset: Utc }; /// The maximum possible `Date`. pub const MAX_UTC: Date = Date { date: NaiveDate::MAX, offset: Utc }; } /// Maps the local date to other date with given conversion function. fn map_local(d: &Date, mut f: F) -> Option> where F: FnMut(NaiveDate) -> Option, { f(d.naive_local()).and_then(|date| d.timezone().from_local_date(&date).single()) } impl Date where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new_with_offset(Some(self.naive_local()), None, &self.offset, items) } /// Formats the date with the specified format string. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Formats the date with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, locale: Locale, ) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new_with_offset_and_locale( Some(self.naive_local()), None, &self.offset, items, locale, ) } /// Formats the date with the specified format string and locale. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, locale: Locale, ) -> DelayedFormat> { self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) } } impl Datelike for Date { #[inline] fn year(&self) -> i32 { self.naive_local().year() } #[inline] fn month(&self) -> u32 { self.naive_local().month() } #[inline] fn month0(&self) -> u32 { self.naive_local().month0() } #[inline] fn day(&self) -> u32 { self.naive_local().day() } #[inline] fn day0(&self) -> u32 { self.naive_local().day0() } #[inline] fn ordinal(&self) -> u32 { self.naive_local().ordinal() } #[inline] fn ordinal0(&self) -> u32 { self.naive_local().ordinal0() } #[inline] fn weekday(&self) -> Weekday { self.naive_local().weekday() } #[inline] fn iso_week(&self) -> IsoWeek { self.naive_local().iso_week() } #[inline] fn with_year(&self, year: i32) -> Option> { map_local(self, |date| date.with_year(year)) } #[inline] fn with_month(&self, month: u32) -> Option> { map_local(self, |date| date.with_month(month)) } #[inline] fn with_month0(&self, month0: u32) -> Option> { map_local(self, |date| date.with_month0(month0)) } #[inline] fn with_day(&self, day: u32) -> Option> { map_local(self, |date| date.with_day(day)) } #[inline] fn with_day0(&self, day0: u32) -> Option> { map_local(self, |date| date.with_day0(day0)) } #[inline] fn with_ordinal(&self, ordinal: u32) -> Option> { map_local(self, |date| date.with_ordinal(ordinal)) } #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option> { map_local(self, |date| date.with_ordinal0(ordinal0)) } } // we need them as automatic impls cannot handle associated types impl Copy for Date where ::Offset: Copy {} unsafe impl Send for Date where ::Offset: Send {} impl PartialEq> for Date { fn eq(&self, other: &Date) -> bool { self.date == other.date } } impl Eq for Date {} impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { self.date.partial_cmp(&other.date) } } impl Ord for Date { fn cmp(&self, other: &Date) -> Ordering { self.date.cmp(&other.date) } } impl hash::Hash for Date { fn hash(&self, state: &mut H) { self.date.hash(state) } } impl Add for Date { type Output = Date; #[inline] fn add(self, rhs: OldDuration) -> Date { self.checked_add_signed(rhs).expect("`Date + Duration` overflowed") } } impl AddAssign for Date { #[inline] fn add_assign(&mut self, rhs: OldDuration) { self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed"); } } impl Sub for Date { type Output = Date; #[inline] fn sub(self, rhs: OldDuration) -> Date { self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed") } } impl SubAssign for Date { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed"); } } impl Sub> for Date { type Output = OldDuration; #[inline] fn sub(self, rhs: Date) -> OldDuration { self.signed_duration_since(rhs) } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.naive_local().fmt(f)?; self.offset.fmt(f) } } impl fmt::Display for Date where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.naive_local().fmt(f)?; self.offset.fmt(f) } } // Note that implementation of Arbitrary cannot be automatically derived for Date, due to // the nontrivial bound ::Offset: Arbitrary. #[cfg(feature = "arbitrary")] impl<'a, Tz> arbitrary::Arbitrary<'a> for Date where Tz: TimeZone, ::Offset: arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { let date = NaiveDate::arbitrary(u)?; let offset = ::Offset::arbitrary(u)?; Ok(Date::from_utc(date, offset)) } } #[cfg(test)] mod tests { use super::Date; use crate::duration::Duration; use crate::{FixedOffset, NaiveDate, 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() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. let two_year_ago = Utc::today() - Duration::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() + Duration::weeks(12); assert_eq!(Utc::today().years_since(future), None); } #[test] fn test_date_add_assign() { let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Date::::from_utc(naivedate, Utc); let mut date_add = date; date_add += Duration::days(5); assert_eq!(date_add, date + Duration::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 + Duration::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 + Duration::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 += Duration::days(5); assert_eq!(date_add, date + Duration::days(5)); } #[test] fn test_date_sub_assign() { let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Date::::from_utc(naivedate, Utc); let mut date_sub = date; date_sub -= Duration::days(5); assert_eq!(date_sub, date - Duration::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 - Duration::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 - Duration::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 -= Duration::days(5); assert_eq!(date_sub, date - Duration::days(5)); } } chrono-0.4.31/src/datetime/mod.rs000064400000000000000000001631330072674642500147500ustar 00000000000000// 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}; use crate::duration::Duration as OldDuration; #[cfg(feature = "unstable-locales")] use crate::format::Locale; use crate::format::{ parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, TOO_LONG, }; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{write_rfc3339, DelayedFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; #[allow(deprecated)] use crate::Date; use crate::{Datelike, Months, Timelike, Weekday}; #[cfg(feature = "rkyv")] 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; /// 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](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)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct DateTime { datetime: NaiveDateTime, offset: Tz::Offset, } /// The minimum possible `DateTime`. #[deprecated(since = "0.4.20", note = "Use DateTime::MIN_UTC instead")] pub const MIN_DATETIME: DateTime = DateTime::::MIN_UTC; /// The maximum possible `DateTime`. #[deprecated(since = "0.4.20", note = "Use DateTime::MAX_UTC instead")] pub const MAX_DATETIME: DateTime = DateTime::::MAX_UTC; impl DateTime { /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. /// /// This is a low-level method, intended for use cases such as deserializing a `DateTime` or /// passing it through FFI. /// /// For regular use you will probably want to use a method such as /// [`TimeZone::from_local_datetime`] or [`NaiveDateTime::and_local_timezone`] instead. /// /// # Example /// #[cfg_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::::from_naive_utc_and_offset(naive_utc, offset); /// assert_eq!(dt, dt_new); /// ``` #[inline] #[must_use] pub fn from_naive_utc_and_offset(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { DateTime { datetime, offset } } /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. #[inline] #[must_use] #[deprecated( since = "0.4.27", note = "Use TimeZone::from_utc_datetime() or DateTime::from_naive_utc_and_offset instead" )] pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { DateTime { datetime, offset } } /// Makes a new `DateTime` from a `NaiveDateTime` in *local* time and an `Offset`. /// /// # Panics /// /// Panics if the local datetime can't be converted to UTC because it would be out of range. /// /// This can happen if `datetime` is near the end of the representable range of `NaiveDateTime`, /// and the offset from UTC pushes it beyond that. #[inline] #[must_use] #[deprecated( since = "0.4.27", note = "Use TimeZone::from_local_datetime() or NaiveDateTime::and_local_timezone instead" )] pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { let datetime_utc = datetime - offset.fix(); DateTime { datetime: datetime_utc, offset } } /// Retrieves the date component with an associated timezone. /// /// Unless you are immediately planning on turning this into a `DateTime` /// with the same timezone you should use the [`date_naive`](DateTime::date_naive) method. /// /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, /// so should be preferred to [`Date`] any time you truly want to operate on dates. /// /// # Panics /// /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This /// method will panic if the offset from UTC would push the local date outside of the /// representable range of a [`Date`]. #[inline] #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] #[allow(deprecated)] #[must_use] pub fn date(&self) -> Date { Date::from_utc(self.naive_local().date(), self.offset.clone()) } /// Retrieves the date component. /// /// # Panics /// /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This /// method will panic if the offset from UTC would push the local date outside of the /// representable range of a [`NaiveDate`]. /// /// # Example /// /// ``` /// use chrono::prelude::*; /// /// let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); /// let other: DateTime = FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); /// assert_eq!(date.date_naive(), other.date_naive()); /// ``` #[inline] #[must_use] pub fn date_naive(&self) -> NaiveDate { 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.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 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 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 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:44.0 and /// 2262-04-11T23:47:16.854775804. #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { self.timestamp_nanos_opt() .expect("value can not be represented in a timestamp with nanosecond precision.") } /// 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:44.0 and /// 2262-04-11T23:47:16.854775804. /// /// # 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)); /// ``` #[inline] #[must_use] pub fn timestamp_nanos_opt(&self) -> Option { 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 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 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 fn timestamp_subsec_nanos(&self) -> u32 { self.datetime.timestamp_subsec_nanos() } /// 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) } /// Changes the associated time zone. /// The returned `DateTime` references the same instant of time from the perspective of the /// provided time zone. #[inline] #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> DateTime { tz.from_utc_datetime(&self.datetime) } /// Fix the offset from UTC to its current value, dropping the associated timezone information. /// This it useful for converting a generic `DateTime` to `DateTime`. #[inline] #[must_use] pub fn fixed_offset(&self) -> DateTime { self.with_timezone(&self.offset().fix()) } /// Adds given `Duration` 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: OldDuration) -> Option> { let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } /// Adds given `Months` to the current date and time. /// /// Uses the last day of the month if the day does not exist in the resulting month. /// /// See [`NaiveDate::checked_add_months`] for more details on behavior. /// /// # Errors /// /// Returns `None` if: /// - The 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> { self.naive_local() .checked_add_months(rhs)? .and_local_timezone(Tz::from_offset(&self.offset)) .single() } /// Subtracts given `Duration` 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: OldDuration) -> Option> { let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } /// Subtracts given `Months` from the current date and time. /// /// Uses the last day of the month if the day does not exist in the resulting month. /// /// See [`NaiveDate::checked_sub_months`] for more details on behavior. /// /// # Errors /// /// Returns `None` if: /// - The 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> { 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.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.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( self, rhs: impl Borrow>, ) -> OldDuration { self.datetime.signed_duration_since(rhs.borrow().datetime) } /// Returns a view to the naive UTC datetime. #[inline] #[must_use] pub 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 + self.offset.fix() } /// Retrieve the elapsed years from now to the given [`DateTime`]. /// /// # Errors /// /// Returns `None` if `base < self`. #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); let earlier_time = (self.month(), self.day(), self.time()) < (base.month(), base.day(), base.time()); years -= match earlier_time { true => 1, false => 0, }; match years >= 0 { true => Some(years as u32), false => None, } } /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. /// /// # Panics /// /// Panics if the date can not be represented in this format: the year may not be negative and /// can not have more than 4 digits. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); crate::format::write_rfc2822(&mut result, self.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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[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.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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[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`. pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; /// The maximum possible `DateTime`. pub const MAX_UTC: DateTime = DateTime { datetime: NaiveDateTime::MAX, offset: Utc }; } impl DateTime { /// Makes a new [`DateTime`] from the number of non-leap seconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// 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 = DateTime::::from_timestamp(1431648000, 0).expect("invalid timestamp"); /// /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); /// ``` #[inline] #[must_use] pub fn from_timestamp(secs: i64, nsecs: u32) -> Option { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::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 { fn default() -> Self { Utc.from_utc_datetime(&NaiveDateTime::default()) } } #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Default for DateTime { fn default() -> Self { Local.from_utc_datetime(&NaiveDateTime::default()) } } impl Default for DateTime { fn default() -> Self { FixedOffset::west_opt(0).unwrap().from_utc_datetime(&NaiveDateTime::default()) } } /// Convert a `DateTime` instance into a `DateTime` instance. impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by /// this will be created with a fixed timezone offset of 0. fn from(src: DateTime) -> Self { src.with_timezone(&FixedOffset::east_opt(0).unwrap()) } } /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. fn from(src: DateTime) -> Self { src.with_timezone(&Local) } } /// Convert a `DateTime` instance into a `DateTime` instance. impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone /// difference. fn from(src: DateTime) -> Self { src.with_timezone(&Utc) } } /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local /// time. fn from(src: DateTime) -> Self { src.with_timezone(&Local) } } /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in /// timezones. fn from(src: DateTime) -> Self { src.with_timezone(&Utc) } } /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// /// Conversion is performed via [`DateTime::with_timezone`]. fn from(src: DateTime) -> Self { src.with_timezone(&src.offset().fix()) } } /// Maps the local datetime to other datetime with given conversion function. fn map_local(dt: &DateTime, mut f: F) -> Option> where F: FnMut(NaiveDateTime) -> Option, { f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) } impl DateTime { /// Parses an RFC 2822 date-and-time string into a `DateTime` value. /// /// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`) /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. /// /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP /// and email headers. It is the 2001 revision of RFC 822, and is itself revised as RFC 5322 in /// 2008. /// /// # Support for the obsolete date format /// /// - A 2-digit year is interpreted to be a year in 1950-2049. /// - The standard allows comments and whitespace between many of the tokens. See [4.3] and /// [Appendix A.5] /// - Single letter 'military' time zone names are parsed as a `-0000` offset. /// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because /// the meaning is now ambiguous, the standard says they should be be considered as `-0000` /// unless there is out-of-band information confirming their meaning. /// The exception is `Z`, which remains identical to `+0000`. /// /// [4.3]: https://www.rfc-editor.org/rfc/rfc2822#section-4.3 /// [Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 /// /// # Example /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); parse(&mut parsed, s, ITEMS.iter())?; parsed.to_datetime() } /// Parses an RFC 3339 date-and-time string into a `DateTime` value. /// /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are /// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a /// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly /// encountered variety of RFC 3339 formats. /// /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing /// values in a wide range of formats, only some of which represent actual date-and-time /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { 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` 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> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_datetime() } /// Parses a string from a user-specified format into a `DateTime` value, and a /// slice with the remaining portion of the string. /// /// Note that this method *requires a timezone* in the input string. See /// [`NaiveDateTime::parse_and_remainder`] for a version that does not /// require a timezone in `s`. The returned [`DateTime`] value will have a [`FixedOffset`] /// reflecting the parsed timezone. /// /// See the [`format::strftime` module](./format/strftime/index.html) for supported format /// sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). /// /// # Example /// /// ```rust /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// let (datetime, remainder) = DateTime::parse_and_remainder( /// "2015-02-18 23:16:09 +0200 trailing text", "%Y-%m-%d %H:%M:%S %z").unwrap(); /// assert_eq!( /// datetime, /// FixedOffset::east_opt(2*3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// assert_eq!(remainder, " trailing text"); /// ``` pub fn parse_and_remainder<'a>( s: &'a str, fmt: &str, ) -> ParseResult<(DateTime, &'a str)> { let mut parsed = Parsed::new(); let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_datetime().map(|d| (d, remainder)) } } impl DateTime where Tz::Offset: fmt::Display, { /// Formats the combined date and time with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { let local = self.naive_local(); DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) } /// Formats the combined date and time per the specified format string. /// /// See the [`crate::format::strftime`] module for the supported escape sequences. /// /// # Example /// ```rust /// use chrono::prelude::*; /// /// let date_time: DateTime = Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap(); /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Formats the combined date and time with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, locale: Locale, ) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { let local = self.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(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, locale: Locale, ) -> DelayedFormat> { self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) } } impl Datelike for DateTime { #[inline] fn year(&self) -> i32 { self.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] /// 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> { 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> { 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> { map_local(self, |datetime| datetime.with_month0(month0)) } /// Makes a new `DateTime` with the month number (starting from 0) 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> { map_local(self, |datetime| datetime.with_day(day)) } /// Makes a new `DateTime` with the month number (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> { map_local(self, |datetime| datetime.with_day0(day0)) } /// Makes a new `DateTime` with the month number (starting from 0) 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> { map_local(self, |datetime| datetime.with_ordinal(ordinal)) } /// Makes a new `DateTime` with the month number (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> { map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) } } impl Timelike for DateTime { #[inline] fn hour(&self) -> u32 { self.naive_local().hour() } #[inline] fn minute(&self) -> u32 { self.naive_local().minute() } #[inline] fn second(&self) -> u32 { self.naive_local().second() } #[inline] fn nanosecond(&self) -> u32 { self.naive_local().nanosecond() } /// Makes a new `DateTime` with the hour number changed. /// /// See also the [`NaiveTime::with_hour`] method. /// /// # Errors /// /// Returns `None` if: /// - The value for `hour` is invalid. /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. #[inline] fn with_hour(&self, hour: u32) -> Option> { map_local(self, |datetime| datetime.with_hour(hour)) } /// Makes a new `DateTime` with the minute number changed. /// /// See also the [`NaiveTime::with_minute`] method. /// /// # Errors /// /// - The value for `minute` is invalid. /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. #[inline] fn with_minute(&self, min: u32) -> Option> { map_local(self, |datetime| datetime.with_minute(min)) } /// Makes a new `DateTime` with the second number changed. /// /// As with the [`second`](#method.second) method, /// the input range is restricted to 0 through 59. /// /// See also the [`NaiveTime::with_second`] method. /// /// # Errors /// /// Returns `None` if: /// - The value for `second` is invalid. /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. #[inline] fn with_second(&self, sec: u32) -> Option> { map_local(self, |datetime| datetime.with_second(sec)) } /// Makes a new `DateTime` with nanoseconds since the whole non-leap second changed. /// /// Returns `None` when the resulting `NaiveDateTime` would be invalid. /// As with the [`NaiveDateTime::nanosecond`] method, /// the input range can exceed 1,000,000,000 for leap seconds. /// /// See also the [`NaiveTime::with_nanosecond`] method. /// /// # Errors /// /// Returns `None` if `nanosecond >= 2,000,000,000`. #[inline] fn with_nanosecond(&self, nano: u32) -> Option> { map_local(self, |datetime| datetime.with_nanosecond(nano)) } } // we need them as automatic impls cannot handle associated types impl Copy for DateTime where ::Offset: Copy {} unsafe impl Send for DateTime where ::Offset: Send {} impl PartialEq> for DateTime { fn eq(&self, other: &DateTime) -> bool { self.datetime == other.datetime } } impl Eq for DateTime {} impl PartialOrd> for DateTime { /// Compare two DateTimes based on their true time, ignoring time zones /// /// # Example /// /// ``` /// use chrono::prelude::*; /// /// let earlier = Utc.with_ymd_and_hms(2015, 5, 15, 2, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); /// let later = Utc.with_ymd_and_hms(2015, 5, 15, 3, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); /// /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); /// /// assert!(later > earlier); /// ``` fn partial_cmp(&self, other: &DateTime) -> Option { self.datetime.partial_cmp(&other.datetime) } } impl Ord for DateTime { fn cmp(&self, other: &DateTime) -> Ordering { self.datetime.cmp(&other.datetime) } } impl hash::Hash for DateTime { fn hash(&self, state: &mut H) { self.datetime.hash(state) } } impl Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: OldDuration) -> DateTime { self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") } } impl Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: Duration) -> DateTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") } } impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { let datetime = self.datetime.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed"); let tz = self.timezone(); *self = tz.from_utc_datetime(&datetime); } } impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); *self += rhs; } } impl Add for DateTime { type Output = DateTime; fn add(self, rhs: Months) -> Self::Output { self.checked_add_months(rhs).unwrap() } } impl Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: OldDuration) -> DateTime { self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") } } impl Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: Duration) -> DateTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") } } impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { let datetime = self.datetime.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed"); let tz = self.timezone(); *self = tz.from_utc_datetime(&datetime) } } impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); *self -= rhs; } } impl Sub for DateTime { type Output = DateTime; fn sub(self, rhs: Months) -> Self::Output { self.checked_sub_months(rhs).unwrap() } } impl Sub> for DateTime { type Output = OldDuration; #[inline] fn sub(self, rhs: DateTime) -> OldDuration { self.signed_duration_since(rhs) } } impl Sub<&DateTime> for DateTime { type Output = OldDuration; #[inline] fn sub(self, rhs: &DateTime) -> OldDuration { self.signed_duration_since(rhs) } } impl Add for DateTime { type Output = DateTime; fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).unwrap() } } impl Sub for DateTime { type Output = DateTime; fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).unwrap() } } impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.naive_local().fmt(f)?; self.offset.fmt(f) } } impl fmt::Display for DateTime where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.naive_local().fmt(f)?; f.write_char(' ')?; self.offset.fmt(f) } } /// Accepts a relaxed form of RFC3339. /// A space or a 'T' are accepted as the separator between the date and time /// parts. /// /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, Utc}; /// "2012-12-12T12:12:12Z".parse::>()?; /// "2012-12-12 12:12:12Z".parse::>()?; /// "2012-12-12 12:12:12+0000".parse::>()?; /// "2012-12-12 12:12:12+00:00".parse::>()?; /// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { s.parse::>().map(|dt| dt.with_timezone(&Utc)) } } /// Accepts a relaxed form of RFC3339. /// A space or a 'T' are accepted as the separator between the date and time /// parts. /// /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, Local}; /// "2012-12-12T12:12:12Z".parse::>()?; /// "2012-12-12 12:12:12Z".parse::>()?; /// "2012-12-12 12:12:12+0000".parse::>()?; /// "2012-12-12 12:12:12+00:00".parse::>()?; /// # Ok::<(), chrono::ParseError>(()) /// ``` #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { s.parse::>().map(|dt| dt.with_timezone(&Local)) } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), Err(e) => { // unlikely but should be handled let dur = e.duration(); let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); if nsec == 0 { (-sec, 0) } else { (-sec - 1, 1_000_000_000 - nsec) } } }; Utc.timestamp_opt(sec, nsec).unwrap() } } #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { DateTime::::from(t).with_timezone(&Local) } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { let sec = dt.timestamp(); let nsec = dt.timestamp_subsec_nanos(); if sec < 0 { // unlikely but should be handled UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) } else { UNIX_EPOCH + Duration::new(sec as u64, nsec) } } } #[cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] #[cfg_attr( docsrs, doc(cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))) )] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { DateTime::::from(&date) } } #[cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] #[cfg_attr( docsrs, doc(cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))) )] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { Utc.timestamp_millis_opt(date.get_time() as i64).unwrap() } } #[cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] #[cfg_attr( docsrs, doc(cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))) )] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. fn from(date: DateTime) -> js_sys::Date { let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); js_sys::Date::new(&js_millis) } } // Note that implementation of Arbitrary cannot be simply derived for DateTime, due to // the nontrivial bound ::Offset: Arbitrary. #[cfg(feature = "arbitrary")] impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime where Tz: TimeZone, ::Offset: arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { let datetime = NaiveDateTime::arbitrary(u)?; let offset = ::Offset::arbitrary(u)?; Ok(DateTime::from_utc(datetime, offset)) } } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where FUtc: Fn(&DateTime) -> Result, FFixed: Fn(&DateTime) -> Result, E: ::core::fmt::Debug, { assert_eq!( to_string_utc(&Utc.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( utc_from_str: FUtc, fixed_from_str: FFixed, local_from_str: FLocal, ) where FUtc: Fn(&str) -> Result, E>, FFixed: Fn(&str) -> Result, E>, FLocal: Fn(&str) -> Result, E>, E: ::core::fmt::Debug, { // should check against the offset as well (the normal DateTime comparison will ignore them) fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { dt.as_ref().map(|dt| (dt, dt.offset())) } assert_eq!( norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), norm(&Some(Utc.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( utc_from_str: FUtc, fixed_from_str: FFixed, local_from_str: FLocal, ) where FUtc: Fn(&str) -> Result, E>, FFixed: Fn(&str) -> Result, E>, FLocal: Fn(&str) -> Result, E>, E: ::core::fmt::Debug, { fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { dt.as_ref().map(|dt| (dt, dt.offset())) } assert_eq!( norm(&utc_from_str("0").ok().map(DateTime::from)), norm(&Some(Utc.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() ); } chrono-0.4.31/src/datetime/rustc_serialize.rs000064400000000000000000000073620072674642500174010ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] use super::{DateTime, 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 Encodable for DateTime { fn encode(&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(me: LocalResult, d: &mut D) -> Result where D: Decoder, T: fmt::Display, { match me { LocalResult::None => Err(d.error("value is not a legal timestamp")), LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), LocalResult::Single(val) => Ok(val), } } impl Decodable for DateTime { fn decode(d: &mut D) -> Result, D::Error> { d.read_str()?.parse::>().map_err(|_| d.error("invalid date and time")) } } #[allow(deprecated)] impl Decodable for TsSeconds { #[allow(deprecated)] fn decode(d: &mut D) -> Result, D::Error> { from(FixedOffset::east_opt(0).unwrap().timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) } } impl Decodable for DateTime { fn decode(d: &mut D) -> Result, D::Error> { d.read_str()? .parse::>() .map(|dt| dt.with_timezone(&Utc)) .map_err(|_| d.error("invalid date and time")) } } /// A [`DateTime`] that can be deserialized from a timestamp /// /// A timestamp here is seconds since the epoch #[derive(Debug)] pub struct TsSeconds(DateTime); #[allow(deprecated)] impl From> for DateTime { /// Pull the inner `DateTime` out #[allow(deprecated)] fn from(obj: TsSeconds) -> DateTime { obj.0 } } #[allow(deprecated)] impl Deref for TsSeconds { type Target = DateTime; fn deref(&self) -> &Self::Target { &self.0 } } #[allow(deprecated)] impl Decodable for TsSeconds { fn decode(d: &mut D) -> Result, D::Error> { from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) } } #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Decodable for DateTime { fn decode(d: &mut D) -> Result, D::Error> { match d.read_str()?.parse::>() { Ok(dt) => Ok(dt.with_timezone(&Local)), Err(_) => Err(d.error("invalid date and time")), } } } #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[allow(deprecated)] impl Decodable for TsSeconds { #[allow(deprecated)] fn decode(d: &mut D) -> Result, D::Error> { from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(|dt| TsSeconds(dt.with_timezone(&Local))) } } #[cfg(test)] 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); } } chrono-0.4.31/src/datetime/serde.rs000064400000000000000000001174620072674642500152770ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] use core::fmt; use serde::{de, ser}; use super::{DateTime, SecondsFormat}; use crate::format::write_rfc3339; 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 ser::Serialize for DateTime { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { struct FormatIso8601<'a, Tz: TimeZone> { inner: &'a DateTime, } 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; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a formatted date and time string or a unix timestamp") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(E::custom) } } /// Deserialize a value that optionally includes a timezone offset in its /// string representation /// /// The value to be deserialized must be an rfc3339 string. /// /// See [the `serde` module](./serde/index.html) for alternate /// deserialization formats. impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(DateTimeVisitor) } } /// Deserialize into a UTC value /// /// The value to be deserialized must be an rfc3339 string. /// /// See [the `serde` module](./serde/index.html) for alternate /// deserialization formats. impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) } } /// Deserialize a value that includes no timezone in its string /// representation /// /// The value to be deserialized must be an rfc3339 string. /// /// See [the `serde` module](./serde/index.html) for alternate /// serialization formats. #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) } } /// Ser/de to/from timestamps in nanoseconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds")] /// time: DateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_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 /// } /// /// 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(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( "value out of range for a timestamp with nanosecond precision", ))?) } /// Deserialize a [`DateTime`] from a nanosecond timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_999).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, { d.deserialize_i64(NanoSecondsTimestampVisitor) } impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in nanoseconds") } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_i64(self, value: i64) -> Result where E: de::Error, { serde_from( Utc.timestamp_opt( value.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(self, value: u64) -> Result where E: de::Error, { serde_from( Utc.timestamp_opt((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32), &value, ) } } } /// Ser/de to/from optional timestamps in nanoseconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds_option")] /// time: Option> /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_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> /// } /// /// 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(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( ser::Error::custom("value out of range for a timestamp with nanosecond precision"), )?), None => serializer.serialize_none(), } } /// Deserialize a `DateTime` from a nanosecond timestamp or none /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionNanoSecondsTimestampVisitor) } struct OptionNanoSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { type Value = Option>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in nanoseconds or none") } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Ser/de to/from timestamps in microseconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds")] /// time: DateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_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 /// } /// /// 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(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_i64(dt.timestamp_micros()) } /// Deserialize a `DateTime` from a microsecond timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, { d.deserialize_i64(MicroSecondsTimestampVisitor) } impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in microseconds") } /// Deserialize a timestamp in milliseconds since the epoch fn visit_i64(self, value: i64) -> Result where E: de::Error, { serde_from( Utc.timestamp_opt( value.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(self, value: u64) -> Result where E: de::Error, { serde_from( Utc.timestamp_opt((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32), &value, ) } } } /// Ser/de to/from optional timestamps in microseconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds_option")] /// time: Option> /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_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> /// } /// /// 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(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), None => serializer.serialize_none(), } } /// Deserialize a `DateTime` from a microsecond timestamp or none /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionMicroSecondsTimestampVisitor) } struct OptionMicroSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { type Value = Option>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in microseconds or none") } /// Deserialize a timestamp in microseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in microseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in microseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Ser/de to/from timestamps in milliseconds /// /// Intended for use with `serde`s `with` attribute. /// /// # Example /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds")] /// time: DateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_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 /// } /// /// 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(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_i64(dt.timestamp_millis()) } /// Deserialize a `DateTime` from a millisecond timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_000_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, { d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) } impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in milliseconds") } /// Deserialize a timestamp in milliseconds since the epoch fn visit_i64(self, value: i64) -> Result where E: de::Error, { serde_from(Utc.timestamp_millis_opt(value), &value) } /// Deserialize a timestamp in milliseconds since the epoch fn visit_u64(self, value: u64) -> Result where E: de::Error, { serde_from( Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), &value, ) } } } /// Ser/de to/from optional timestamps in milliseconds /// /// Intended for use with `serde`s `with` attribute. /// /// # Example /// /// ```rust /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds_option")] /// time: Option> /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_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> /// } /// /// 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(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), None => serializer.serialize_none(), } } /// Deserialize a `DateTime` from a millisecond timestamp or none /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{TimeZone, DateTime, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; /// /// #[derive(Deserialize, PartialEq, Debug)] /// #[serde(untagged)] /// enum E { /// V(T), /// } /// /// #[derive(Deserialize, PartialEq, Debug)] /// struct S { /// #[serde(default, deserialize_with = "from_milli_tsopt")] /// time: Option> /// } /// /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp_opt(1526522699, 918000000).unwrap()) })); /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; /// assert_eq!(s, E::V(S { time: None })); /// let t: E = serde_json::from_str(r#"{}"#)?; /// assert_eq!(t, E::V(S { time: None })); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionMilliSecondsTimestampVisitor) .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) } struct OptionMilliSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { type Value = Option>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in milliseconds or none") } /// Deserialize a timestamp in milliseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in milliseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in milliseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Ser/de to/from timestamps in seconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{TimeZone, DateTime, Utc}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_seconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds")] /// time: DateTime /// } /// /// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds { use core::fmt; use serde::{de, ser}; use 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 /// } /// /// let my_s = S { /// time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_i64(dt.timestamp()) } /// Deserialize a `DateTime` from a seconds timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds::deserialize as from_ts; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, { d.deserialize_i64(SecondsTimestampVisitor) } impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in seconds") } /// Deserialize a timestamp in seconds since the epoch fn visit_i64(self, value: i64) -> Result where E: de::Error, { serde_from(Utc.timestamp_opt(value, 0), &value) } /// Deserialize a timestamp in seconds since the epoch fn visit_u64(self, value: u64) -> Result where E: de::Error, { serde_from( 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> /// } /// /// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds_option { use core::fmt; use serde::{de, ser}; use super::SecondsTimestampVisitor; use crate::{DateTime, Utc}; /// Serialize a UTC datetime into an integer number of seconds since the epoch or none /// /// Intended for use with `serde`s `serialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{TimeZone, DateTime, Utc}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_tsopt")] /// time: Option> /// } /// /// let my_s = S { /// time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.timestamp()), None => serializer.serialize_none(), } } /// Deserialize a `DateTime` from a seconds timestamp or none /// /// Intended for use with `serde`s `deserialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionSecondsTimestampVisitor) } struct OptionSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { type Value = Option>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in seconds or none") } /// Deserialize a timestamp in seconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(SecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in seconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in seconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } #[cfg(test)] mod tests { #[cfg(feature = "clock")] use crate::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 = 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 { LocalResult::Single(TestTimeZone) } fn offset_from_local_datetime( &self, _local: &NaiveDateTime, ) -> LocalResult { 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 = serde_json::from_str(&encoded).unwrap(); assert_eq!(dt, decoded); assert_eq!(dt.offset().fix(), *decoded.offset()); } } chrono-0.4.31/src/datetime/tests.rs000064400000000000000000001570660072674642500153430ustar 00000000000000use super::DateTime; use crate::duration::Duration as OldDuration; 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, Timelike}; #[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 { unimplemented!() } fn offset_from_local_datetime( &self, local: &NaiveDateTime, ) -> crate::LocalResult { 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() - OldDuration::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() + OldDuration::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 #[allow(clippy::too_many_arguments)] fn ymdhms( fixedoffset: &FixedOffset, year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, ) -> DateTime { fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() } // local helper function to easily create a DateTime #[allow(clippy::too_many_arguments)] fn ymdhms_milli( fixedoffset: &FixedOffset, year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, milli: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() .with_nanosecond(milli * 1_000_000) .unwrap() } // local helper function to easily create a DateTime #[allow(clippy::too_many_arguments)] #[cfg(any(feature = "alloc", feature = "std"))] fn ymdhms_micro( fixedoffset: &FixedOffset, year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, micro: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() .with_nanosecond(micro * 1000) .unwrap() } // local helper function to easily create a DateTime #[allow(clippy::too_many_arguments)] #[cfg(any(feature = "alloc", feature = "std"))] fn ymdhms_nano( fixedoffset: &FixedOffset, year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, nano: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() .with_nanosecond(nano) .unwrap() } // local helper function to easily create a DateTime #[cfg(any(feature = "alloc", feature = "std"))] fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() } // local helper function to easily create a DateTime fn ymdhms_milli_utc( year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, milli: u32, ) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() .with_nanosecond(milli * 1_000_000) .unwrap() } #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); assert_eq!( format!("{}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 UTC" ); assert_eq!( format!("{}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 -04:00" ); assert_eq!( format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 +09:00" ); assert_eq!( format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09Z" ); assert_eq!( format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09-04:00" ); assert_eq!( format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09+09:00" ); // edge cases assert_eq!( format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00Z" ); assert_eq!( format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00-04:00" ); assert_eq!( format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00+09:00" ); assert_eq!( format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59Z" ); assert_eq!( format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59-04:00" ); assert_eq!( format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59+09:00" ); let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( dt + OldDuration::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()), OldDuration::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 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); let _ = dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); } #[test] fn test_datetime_from_str() { assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east_opt(0) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15Utc".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east_opt(0) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(FixedOffset::west_opt(10 * 3600) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(13, 16, 9, 150) .unwrap() ) .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert!("2015-02-18T23:16:9.15øøø".parse::>().is_err()); // no test for `DateTime`, we cannot verify that much. } #[test] fn test_parse_datetime_utc() { // valid cases let valid = [ "2001-02-03T04:05:06Z", "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", "2001-02-03T04:05:06-01:00", "2012-12-12 12:12:12Z", "2012-12-12t12:12:12Z", "2012-12-12T12:12:12Z", "2012 -12-12T12:12:12Z", "2012 -12-12T12:12:12Z", "2012- 12-12T12:12:12Z", "2012- 12-12T12:12:12Z", "2012-12-12T 12:12:12Z", "2012-12-12T12 :12:12Z", "2012-12-12T12 :12:12Z", "2012-12-12T12: 12:12Z", "2012-12-12T12: 12:12Z", "2012-12-12T12 : 12:12Z", "2012-12-12T12:12:12Z ", " 2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", "+2015-2-18T23:16:09.153Z", "-77-02-18T23:16:09Z", "+82701-05-6T15:9:60.898989898989Z", ]; for &s in &valid { eprintln!("test_parse_datetime_utc valid {:?}", s); let d = match s.parse::>() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; let s_ = format!("{:?}", d); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::>() { Ok(d) => d, Err(e) => { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result `{:?}` does not match", s, d, d_ ); } // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ "", // empty "Z", // missing data "15Z", // missing data "15:8:9Z", // missing date "15-8-9Z", // missing time or date "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format "1441497364.649", // valid datetime, wrong format "+1441497364.649", // valid datetime, wrong format "+1441497364", // valid datetime, wrong format "+1441497364Z", // valid datetime, wrong format "2014/02/03 04:05:06Z", // valid datetime, wrong format "2001-02-03T04:05:0600:00", // valid datetime, timezone too close "2015-15-15T15:15:15Z", // invalid datetime "2012-12-12T12:12:12x", // invalid timezone "2012-123-12T12:12:12Z", // invalid month "2012-12-77T12:12:12Z", // invalid day "2012-12-12T26:12:12Z", // invalid hour "2012-12-12T12:61:12Z", // invalid minute "2012-12-12T12:12:62Z", // invalid second "2012-12-12 T12:12:12Z", // space after date "2012-12-12T12:12:12ZZ", // trailing literal 'Z' "+802701-12-12T12:12:12Z", // invalid year (out of bounds) "+ 2012-12-12T12:12:12Z", // invalid space before year " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; for &s in &invalid { eprintln!("test_parse_datetime_utc invalid {:?}", s); assert!(s.parse::>().is_err()); } } #[test] fn test_parse_from_str() { let edt = FixedOffset::east_opt(570 * 60).unwrap(); let edt0 = FixedOffset::east_opt(0).unwrap(); let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); assert_eq!( DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") .is_err()); assert_eq!( DateTime::parse_from_str("0", "%s").unwrap(), NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); // no test for `DateTime`, we cannot verify that much. } #[test] fn test_datetime_parse_from_str() { let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); let parse = DateTime::parse_from_str; // timezone variations // // %Z // // wrong timezone format assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data? assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); // // %z // assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing newline after timezone assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing colon assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); // trailing colon with space assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); // trailing colon, mismatch space assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); // wrong timezone data assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); // // %:z // assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:"), Ok(dt)); // wrong timezone data assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // timezone data hs too many colons assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); // timezone data hs too many colons assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); // // %::z // assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); // mismatching colon expectations assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // wrong timezone data assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); // mismatching colons and spaces assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); // mismatching colons expectations assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); // mismatching colons expectations mid-string assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); // mismatching colons expectations, before end assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); // // %:::z // assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); // wrong timezone data assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); // // %::::z // // too many colons assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // // %#z // assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); assert_eq!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); assert_eq!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // timezone with partial minutes adjacent hours assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // bad timezone data assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) with trailing space assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); // bad timezone data (partial minutes) mid-string assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // bad timezone data assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // timezone data ambiguous with hours assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); } #[test] fn test_to_string_round_trip() { let dt = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let _dt: DateTime = dt.to_string().parse().unwrap(); let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap()); let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap()); let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); } #[test] #[cfg(feature = "clock")] fn test_to_string_round_trip_with_local() { let ndt = Local::now(); let _dt: DateTime = ndt.to_string().parse().unwrap(); } #[test] #[cfg(feature = "clock")] fn test_datetime_format_with_local() { // if we are not around the year boundary, local and UTC date should have the same year let dt = Local::now().with_month(5).unwrap(); assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); } #[test] fn test_datetime_is_send_and_copy() { fn _assert_send_copy() {} // UTC is known to be `Send + Copy`. _assert_send_copy::>(); } #[test] fn test_subsecond_part() { let datetime = Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2014, 7, 8) .unwrap() .and_hms_nano_opt(9, 10, 11, 1234567) .unwrap(), ) .unwrap(); assert_eq!(1, datetime.timestamp_subsec_millis()); assert_eq!(1234, datetime.timestamp_subsec_micros()); assert_eq!(1234567, datetime.timestamp_subsec_nanos()); } // Some targets, such as `wasm32-wasi`, have a problematic definition of `SystemTime`, such as an // `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`). // See https://github.com/rust-lang/rust/issues/44394. #[test] #[cfg(all(feature = "std", not(all(target_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 assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); assert_eq!( DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), Utc.from_local_datetime( &NaiveDate::from_ymd_opt(2001, 9, 9) .unwrap() .and_hms_nano_opt(1, 46, 39, nanos) .unwrap() ) .unwrap() ); assert_eq!( DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), Utc.from_local_datetime( &NaiveDate::from_ymd_opt(1938, 4, 24) .unwrap() .and_hms_nano_opt(22, 13, 20, 1_000) .unwrap() ) .unwrap() ); // DateTime -> SystemTime assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); assert_eq!( SystemTime::from( Utc.from_local_datetime( &NaiveDate::from_ymd_opt(2001, 9, 9) .unwrap() .and_hms_nano_opt(1, 46, 39, nanos) .unwrap() ) .unwrap() ), UNIX_EPOCH + Duration::new(999_999_999, nanos) ); assert_eq!( SystemTime::from( Utc.from_local_datetime( &NaiveDate::from_ymd_opt(1938, 4, 24) .unwrap() .and_hms_nano_opt(22, 13, 20, 1_000) .unwrap() ) .unwrap() ), UNIX_EPOCH - Duration::new(999_999_999, nanos) ); // DateTime -> SystemTime (via `with_timezone`) #[cfg(feature = "clock")] { assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); } assert_eq!( SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), UNIX_EPOCH ); assert_eq!( SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), UNIX_EPOCH ); } #[test] #[allow(deprecated)] fn test_datetime_from_local() { // 2000-01-12T02:00:00Z let naivedatetime_utc = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); // 2000-01-12T10:00:00+8:00:00 let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); let naivedatetime_east = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); // 2000-01-11T19:00:00-7:00:00 let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); let naivedatetime_west = NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); } #[test] #[cfg(feature = "clock")] fn test_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() - OldDuration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. let two_year_ago = Utc::now().date_naive() - OldDuration::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() + OldDuration::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 += OldDuration::seconds(60); assert_eq!(datetime_add, datetime + OldDuration::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 + OldDuration::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 + OldDuration::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 += OldDuration::days(1); assert_eq!(datetime_add, datetime + OldDuration::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 -= OldDuration::minutes(90); assert_eq!(datetime_sub, datetime - OldDuration::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 - OldDuration::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 - OldDuration::minutes(90)); } #[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 -= OldDuration::days(1); assert_eq!(datetime_sub, datetime - OldDuration::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(all(target_os = "windows", feature = "clock"))] fn test_from_naive_date_time_windows() { let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); let max_year = NaiveDate::from_ymd_opt(30827, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); let too_low_year = NaiveDate::from_ymd_opt(1600, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); let too_high_year = NaiveDate::from_ymd_opt(30829, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); let _ = Local.from_utc_datetime(&min_year); let _ = Local.from_utc_datetime(&max_year); let _ = Local.from_local_datetime(&min_year); let _ = Local.from_local_datetime(&max_year); let local_too_low = Local.from_local_datetime(&too_low_year); let local_too_high = Local.from_local_datetime(&too_high_year); assert_eq!(local_too_low, LocalResult::None); assert_eq!(local_too_high, LocalResult::None); let err = std::panic::catch_unwind(|| { Local.from_utc_datetime(&too_low_year); }); assert!(err.is_err()); let err = std::panic::catch_unwind(|| { Local.from_utc_datetime(&too_high_year); }); assert!(err.is_err()); } #[test] #[cfg(feature = "clock")] fn test_datetime_local_from_preserves_offset() { let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = Local.from_utc_datetime(&naivedatetime); let offset = datetime.offset().fix(); let datetime_fixed: DateTime = datetime.into(); assert_eq!(&offset, datetime_fixed.offset()); assert_eq!(datetime.fixed_offset(), datetime_fixed); } #[test] fn test_datetime_fixed_offset() { let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = Utc.from_utc_datetime(&naivedatetime); let fixed_utc = FixedOffset::east_opt(0).unwrap(); assert_eq!(datetime.fixed_offset(), fixed_utc.from_local_datetime(&naivedatetime).unwrap()); let fixed_offset = FixedOffset::east_opt(3600).unwrap(); let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap(); assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); } #[test] fn test_add_sub_months() { let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap()); let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap(); assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap()); let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap()); let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap(); assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap()); } #[test] fn test_auto_conversion() { let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) .unwrap() .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) .unwrap(); let utc_dt2: DateTime = cdt_dt.into(); assert_eq!(utc_dt, utc_dt2); } #[test] #[cfg(feature = "clock")] #[allow(deprecated)] fn test_test_deprecated_from_offset() { let now = Local::now(); let naive = now.naive_local(); let utc = now.naive_utc(); let offset: FixedOffset = *now.offset(); assert_eq!(DateTime::::from_local(naive, offset), now); assert_eq!(DateTime::::from_utc(utc, offset), now); } #[test] #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] fn locale_decimal_point() { use crate::Locale::{ar_SY, nl_NL}; let dt = Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap(); assert_eq!(dt.format_localized("%T%.f", nl_NL).to_string(), "18:58:00,123456780"); assert_eq!(dt.format_localized("%T%.3f", nl_NL).to_string(), "18:58:00,123"); assert_eq!(dt.format_localized("%T%.6f", nl_NL).to_string(), "18:58:00,123456"); assert_eq!(dt.format_localized("%T%.9f", nl_NL).to_string(), "18:58:00,123456780"); assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00.123456780"); assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00.123"); assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); } /// This is an extended test for . #[test] fn nano_roundrip() { const BILLION: i64 = 1_000_000_000; for nanos in [ i64::MIN, i64::MIN + 1, i64::MIN + 2, i64::MIN + BILLION - 1, i64::MIN + BILLION, i64::MIN + BILLION + 1, -BILLION - 1, -BILLION, -BILLION + 1, 0, BILLION - 1, BILLION, BILLION + 1, i64::MAX - BILLION - 1, i64::MAX - BILLION, i64::MAX - BILLION + 1, i64::MAX - 2, i64::MAX - 1, i64::MAX, ] { println!("nanos: {}", nanos); let dt = Utc.timestamp_nanos(nanos); let nanos2 = dt.timestamp_nanos_opt().expect("value roundtrips"); assert_eq!(nanos, nanos2); } } chrono-0.4.31/src/duration.rs000064400000000000000000000706710072674642500142260ustar 00000000000000// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Temporal quantification use core::ops::{Add, Div, Mul, Neg, Sub}; use core::time::Duration as StdDuration; use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "rkyv")] 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. 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; 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, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Duration { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC } /// The minimum possible `Duration`: `i64::MIN` milliseconds. pub(crate) 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(crate) 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] #[must_use] 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] #[must_use] 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] #[must_use] 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] #[must_use] 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` milliseconds /// or less than `i64::MIN` milliseconds. #[inline] #[must_use] 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 const 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, nanos } } /// Makes a new `Duration` with given number of microseconds. #[inline] pub const 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, nanos } } /// Makes a new `Duration` with given number of nanoseconds. #[inline] pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); Duration { secs, nanos: nanos as i32 } } /// Returns the total number of whole weeks in the duration. #[inline] pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } /// Returns the total number of whole days in the duration. pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } /// Returns the total number of whole hours in the duration. #[inline] pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } /// Returns the total number of whole minutes in the duration. #[inline] pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } /// Returns the total number of whole seconds in the duration. 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 /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. const 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 const 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 const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; secs_part.checked_add(nanos_part as i64) } /// Returns the total number of whole nanoseconds in the duration, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); let nanos_part = self.nanos_mod_sec(); secs_part.checked_add(nanos_part as i64) } /// Add two durations, returning `None` if overflow occurred. #[must_use] pub fn checked_add(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); let mut nanos = self.nanos + rhs.nanos; if nanos >= NANOS_PER_SEC { nanos -= NANOS_PER_SEC; secs = try_opt!(secs.checked_add(1)); } let d = Duration { secs, 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. #[must_use] pub fn checked_sub(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); let mut nanos = self.nanos - rhs.nanos; if nanos < 0 { nanos += NANOS_PER_SEC; secs = try_opt!(secs.checked_sub(1)); } let d = Duration { secs, 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 const fn abs(&self) -> Duration { if self.secs < 0 && self.nanos != 0 { Duration { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } } else { Duration { secs: self.secs.abs(), nanos: self.nanos } } } /// The minimum possible `Duration`: `i64::MIN` milliseconds. #[inline] pub const fn min_value() -> Duration { MIN } /// The maximum possible `Duration`: `i64::MAX` milliseconds. #[inline] pub const fn max_value() -> Duration { MAX } /// A duration where the stored seconds and nanoseconds are equal to zero. #[inline] pub const fn zero() -> Duration { Duration { secs: 0, nanos: 0 } } /// Returns `true` if the duration equals `Duration::zero()`. #[inline] pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 } /// Creates a `time::Duration` object from `std::time::Duration` /// /// This function errors when original duration is larger than the maximum /// value supported for this type. pub fn from_std(duration: StdDuration) -> Result { // We need to check secs as u64 before coercing to i64 if duration.as_secs() > MAX.secs as u64 { return Err(OutOfRangeError(())); } let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 }; if d > MAX { return Err(OutOfRangeError(())); } Ok(d) } /// Creates a `std::time::Duration` object from `time::Duration` /// /// This function errors when duration is less than zero. As standard /// library implementation is limited to non-negative values. pub fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); } Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) } } impl Neg for Duration { type Output = Duration; #[inline] fn neg(self) -> Duration { if self.nanos == 0 { Duration { secs: -self.secs, nanos: 0 } } else { Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } } } } impl Add for Duration { type Output = Duration; fn add(self, rhs: Duration) -> Duration { let mut secs = self.secs + rhs.secs; let mut nanos = self.nanos + rhs.nanos; if nanos >= NANOS_PER_SEC { nanos -= NANOS_PER_SEC; secs += 1; } Duration { secs, 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, nanos } } } impl Mul for Duration { type Output = Duration; fn mul(self, rhs: i32) -> Duration { // Multiply nanoseconds as i64, because it cannot overflow that way. let total_nanos = self.nanos as i64 * rhs as i64; let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); let secs = self.secs * rhs as i64 + extra_secs; Duration { secs, nanos: nanos as i32 } } } impl Div for Duration { type Output = Duration; fn div(self, rhs: i32) -> Duration { let mut secs = self.secs / rhs as i64; let carry = self.secs - secs * rhs as i64; let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; let mut nanos = self.nanos / rhs + extra_nanos as i32; if nanos >= NANOS_PER_SEC { nanos -= NANOS_PER_SEC; secs += 1; } if nanos < 0 { nanos += NANOS_PER_SEC; secs -= 1; } Duration { secs, nanos } } } impl<'a> core::iter::Sum<&'a Duration> for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + *x) } } impl core::iter::Sum for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + x) } } impl fmt::Display for Duration { /// Format a duration using the [ISO 8601] format /// /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // technically speaking, negative duration is not valid ISO 8601, // but we need to print it anyway. let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; let days = abs.secs / SECS_PER_DAY; let secs = abs.secs - days * SECS_PER_DAY; let hasdate = days != 0; let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; write!(f, "{}P", sign)?; if hasdate { write!(f, "{}D", days)?; } if hastime { if abs.nanos == 0 { write!(f, "T{}S", secs)?; } else if abs.nanos % NANOS_PER_MILLI == 0 { write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?; } else if abs.nanos % NANOS_PER_MICRO == 0 { write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?; } else { write!(f, "T{}.{:09}S", secs, abs.nanos)?; } } Ok(()) } } /// Represents error when converting `Duration` to/from a standard library /// implementation /// /// The `std::time::Duration` supports a range from zero to `u64::MAX` /// *seconds*, while this module supports signed range of up to /// `i64::MAX` of *milliseconds*. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OutOfRangeError(()); impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Source duration value is out of range for the target type") } } #[cfg(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(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Duration { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { const MIN_SECS: i64 = i64::MIN / 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 = Duration { secs, nanos }; if duration < MIN || duration > MAX { Err(arbitrary::Error::IncorrectFormat) } else { Ok(duration) } } } #[cfg(test)] mod tests { use super::OutOfRangeError; use super::{Duration, MAX, MIN}; use core::time::Duration as StdDuration; #[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(86_399) + Duration::seconds(4), Duration::days(1) + Duration::seconds(3) ); assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863_000)); assert_eq!(Duration::days(10) - Duration::seconds(1_000_000), Duration::seconds(-136_000)); assert_eq!( Duration::days(2) + Duration::seconds(86_399) + Duration::nanoseconds(1_234_567_890), Duration::days(3) + Duration::nanoseconds(234_567_890) ); assert_eq!(-Duration::days(3), Duration::days(-3)); assert_eq!( -(Duration::days(3) + Duration::seconds(70)), Duration::days(-4) + Duration::seconds(86_400 - 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(86_399).num_days(), 0); assert_eq!(Duration::seconds(86_401).num_days(), 1); assert_eq!(Duration::seconds(-86_399).num_days(), 0); assert_eq!(Duration::seconds(-86_401).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 = 86_400_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 = 86_400_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_abs() { assert_eq!(Duration::milliseconds(1300).abs(), Duration::milliseconds(1300)); assert_eq!(Duration::milliseconds(1000).abs(), Duration::milliseconds(1000)); assert_eq!(Duration::milliseconds(300).abs(), Duration::milliseconds(300)); assert_eq!(Duration::milliseconds(0).abs(), Duration::milliseconds(0)); assert_eq!(Duration::milliseconds(-300).abs(), Duration::milliseconds(300)); assert_eq!(Duration::milliseconds(-700).abs(), Duration::milliseconds(700)); assert_eq!(Duration::milliseconds(-1000).abs(), Duration::milliseconds(1000)); assert_eq!(Duration::milliseconds(-1300).abs(), Duration::milliseconds(1300)); assert_eq!(Duration::milliseconds(-1700).abs(), Duration::milliseconds(1700)); } #[test] #[allow(clippy::erasing_op)] 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_sum() { let duration_list_1 = [Duration::zero(), Duration::seconds(1)]; let sum_1: Duration = duration_list_1.iter().sum(); assert_eq!(sum_1, Duration::seconds(1)); let duration_list_2 = [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; let sum_2: Duration = duration_list_2.iter().sum(); assert_eq!(sum_2, Duration::seconds(17)); let duration_arr = [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; let sum_3: Duration = duration_arr.into_iter().sum(); assert_eq!(sum_3, Duration::seconds(17)); } #[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(-86_401).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(86_401).to_std(), Ok(StdDuration::new(86_401, 0))); assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123_000_000))); assert_eq!( Duration::milliseconds(123_765).to_std(), Ok(StdDuration::new(123, 765_000_000)) ); assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); assert_eq!(MAX.to_std(), Ok(StdDuration::new(9_223_372_036_854_775, 807_000_000))); 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(86_401)), Duration::from_std(StdDuration::new(86_401, 0))); assert_eq!( Ok(Duration::milliseconds(123)), Duration::from_std(StdDuration::new(0, 123_000_000)) ); assert_eq!( Ok(Duration::milliseconds(123_765)), Duration::from_std(StdDuration::new(123, 765_000_000)) ); assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777))); assert_eq!( Ok(MAX), Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_000)) ); assert_eq!( Duration::from_std(StdDuration::new(9_223_372_036_854_776, 0)), Err(OutOfRangeError(())) ); assert_eq!( Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_001)), Err(OutOfRangeError(())) ); } } chrono-0.4.31/src/format/formatting.rs000064400000000000000000001174110072674642500160350ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! Date and time formatting routines. #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::fmt; use core::fmt::Write; #[cfg(any( feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize" ))] use crate::datetime::SecondsFormat; #[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::Offset; #[cfg(any( feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize" ))] use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::{NaiveDate, NaiveTime, Weekday}; #[cfg(any(feature = "alloc", feature = "std"))] use super::locales; #[cfg(any( feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize" ))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; #[cfg(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat { /// The date view, if any. date: Option, /// The time view, if any. time: Option, /// The name and local-to-UTC difference for the offset (timezone), if any. off: Option<(String, FixedOffset)>, /// An iterator returning formatting items. items: I, /// Locale used for text. // TODO: Only used with the locale feature. We should make this property // only present when the feature is enabled. #[cfg(feature = "unstable-locales")] locale: Option, } #[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { DelayedFormat { date, time, off: None, items, #[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( date: Option, time: Option, offset: &Off, items: I, ) -> DelayedFormat where Off: Offset + fmt::Display, { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { date, 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")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_locale( date: Option, time: Option, items: I, locale: Locale, ) -> DelayedFormat { DelayedFormat { date, time, off: None, items, locale: Some(locale) } } /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_offset_and_locale( date: Option, time: Option, offset: &Off, items: I, locale: Locale, ) -> DelayedFormat where Off: Offset + fmt::Display, { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } } } #[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] { if let Some(locale) = self.locale { return format_localized( f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone(), locale, ); } } format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) } } /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, items: I, ) -> fmt::Result where I: Iterator + Clone, B: Borrow>, { let mut result = String::new(); for item in items { format_inner(&mut result, date, time, off, item.borrow(), None)?; } w.pad(&result) } /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, item: &Item<'_>, ) -> fmt::Result { let mut result = String::new(); format_inner(&mut result, date, time, off, item, None)?; w.pad(&result) } /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, items: I, locale: Locale, ) -> fmt::Result where I: Iterator + Clone, B: Borrow>, { let mut result = String::new(); for item in items { format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; } w.pad(&result) } /// Formats single formatting item #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_item_localized( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, item: &Item<'_>, locale: Locale, ) -> fmt::Result { let mut result = String::new(); format_inner(&mut result, date, time, off, item, Some(locale))?; w.pad(&result) } #[cfg(any(feature = "alloc", feature = "std"))] fn format_inner( w: &mut impl Write, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, item: &Item<'_>, locale: Option, ) -> fmt::Result { let locale = locale.unwrap_or(default_locale()); match *item { Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(any(feature = "alloc", feature = "std"))] 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_inner(w, *d, *t, off, locale)) } 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 = "std", 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(()) } } /// 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 = "std", 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(any(feature = "alloc", feature = "std"))] /// 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 { write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) } #[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( w: &mut impl Write, d: NaiveDate, t: NaiveTime, off: FixedOffset, locale: Locale, ) -> fmt::Result { let year = d.year(); // RFC2822 is only defined on years 0 through 9999 if !(0..=9999).contains(&year) { return Err(fmt::Error); } w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; w.write_str(", ")?; let day = d.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(locale)[d.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) = t.hms(); write_hundreds(w, hour as u8)?; w.write_char(':')?; write_hundreds(w, min as u8)?; w.write_char(':')?; let sec = sec + t.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(any(feature = "alloc", feature = "std"))] mod tests { use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; use crate::FixedOffset; #[cfg(any(feature = "alloc", feature = "std"))] use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; #[test] #[cfg(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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"], ], ); } } chrono-0.4.31/src/format/locales.rs000064400000000000000000000056620072674642500153110ustar 00000000000000#[cfg(feature = "unstable-locales")] mod localized { use pure_rust_locales::{locale_match, Locale}; pub(crate) const fn default_locale() -> Locale { Locale::POSIX } pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::ABMON) } pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::MON) } pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::ABDAY) } pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::DAY) } 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) const fn t_fmt_ampm(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT_AMPM) } } #[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::*; chrono-0.4.31/src/format/mod.rs000064400000000000000000000472760072674642500144550ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! Formatting (and parsing) utilities for date and time. //! //! This module provides the common types and routines to implement, //! for example, [`DateTime::format`](../struct.DateTime.html#method.format) or //! [`DateTime::parse_from_str`](../struct.DateTime.html#method.parse_from_str) methods. //! For most cases you should use these high-level interfaces. //! //! Internally the formatting and parsing shares the same abstract **formatting items**, //! 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 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")] use alloc::boxed::Box; use core::fmt; use core::str::FromStr; #[cfg(feature = "std")] use std::error::Error; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; 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; #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) mod locales; pub(crate) use formatting::write_hundreds; #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) use formatting::write_rfc2822; #[cfg(any( feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize" ))] pub(crate) use formatting::write_rfc3339; #[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] pub use locales::Locale; #[cfg(all(not(feature = "unstable-locales"), any(feature = "alloc", feature = "std")))] pub(crate) 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, Hash)] enum Void {} /// Padding characters for numeric items. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Pad { /// No padding. None, /// Zero (`0`) padding. Zero, /// Space padding. Space, } /// Numeric item types. /// They have associated formatting width (FW) and parsing width (PW). /// /// The **formatting width** is the minimal width to be formatted. /// If the number is too short, and the padding is not [`Pad::None`](./enum.Pad.html#variant.None), /// then it is left-padded. /// If the number is too long or (in some cases) negative, it is printed as is. /// /// The **parsing width** is the maximal width to be scanned. /// The parser only tries to consume from one to given number of digits (greedily). /// 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, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). /// 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, /// Gregorian year modulo 100 (FW=PW=2). Cannot be negative. YearMod100, /// Year in the ISO week date (FW=4, PW=∞). /// May accept years before 1 BCE or after 9999 CE, given an initial sign. IsoYear, /// Year in the ISO week date, divided by 100 (FW=PW=2). Implies the non-negative year. IsoYearDiv100, /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative. IsoYearMod100, /// Month (FW=PW=2). Month, /// Day of the month (FW=PW=2). Day, /// Week number, where the week 1 starts at the first Sunday of January (FW=PW=2). WeekFromSun, /// Week number, where the week 1 starts at the first Monday of January (FW=PW=2). WeekFromMon, /// Week number in the ISO week date (FW=PW=2). IsoWeek, /// Day of the week, where Sunday = 0 and Saturday = 6 (FW=PW=1). NumDaysFromSun, /// Day of the week, where Monday = 1 and Sunday = 7 (FW=PW=1). WeekdayFromMon, /// Day of the year (FW=PW=3). Ordinal, /// Hour number in the 24-hour clocks (FW=PW=2). Hour, /// Hour number in the 12-hour clocks (FW=PW=2). Hour12, /// The number of minutes since the last whole hour (FW=PW=2). Minute, /// The number of seconds since the last whole minute (FW=PW=2). Second, /// The number of nanoseconds since the last whole second (FW=PW=9). /// Note that this is *not* left-aligned; /// see also [`Fixed::Nanosecond`](./enum.Fixed.html#variant.Nanosecond). Nanosecond, /// The number of non-leap seconds since the midnight UTC on January 1, 1970 (FW=1, PW=∞). /// For formatting, it assumes UTC upon the absence of time zone offset. Timestamp, /// Internal uses only. /// /// This item exists so that one can add additional internal-only formatting /// without breaking major compatibility (as enum variants cannot be selectively private). Internal(InternalNumeric), } /// An opaque type representing numeric item types for internal uses only. #[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") } } /// Fixed-format item types. /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. /// /// Prints a three-letter-long name in the title case, reads the same name in any case. ShortMonthName, /// Full month names. /// /// Prints a full name in the title case, reads either a short or full name in any case. LongMonthName, /// Abbreviated day of the week names. /// /// Prints a three-letter-long name in the title case, reads the same name in any case. ShortWeekdayName, /// Full day of the week names. /// /// Prints a full name in the title case, reads either a short or full name in any case. LongWeekdayName, /// AM/PM. /// /// Prints in lower case, reads in any case. LowerAmPm, /// AM/PM. /// /// Prints in upper case, reads in any case. UpperAmPm, /// An optional dot plus one or more digits for left-aligned nanoseconds. /// May print nothing, 3, 6 or 9 digits according to the available accuracy. /// See also [`Numeric::Nanosecond`](./enum.Numeric.html#variant.Nanosecond). Nanosecond, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3. Nanosecond3, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6. Nanosecond6, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9. Nanosecond9, /// Timezone name. /// /// It does not support parsing, its use in the parser is an immediate failure. TimezoneName, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+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` 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, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColonZ, /// Same as [`TimezoneOffsetColon`](#variant.TimezoneOffsetColon) but prints no colon. /// Parsing allows an optional colon. TimezoneOffset, /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ) but prints no colon. /// Parsing allows an optional colon. TimezoneOffsetZ, /// RFC 2822 date and time syntax. Commonly used for email and MIME date and time. RFC2822, /// RFC 3339 & ISO 8601 date and time syntax. RFC3339, /// Internal uses only. /// /// This item exists so that one can add additional internal-only formatting /// without breaking major compatibility (as enum variants cannot be selectively private). Internal(InternalFixed), } /// An opaque type representing fixed-format item types for internal uses only. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InternalFixed { val: InternalInternal, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). /// /// # Panics /// /// If you try to use this for printing. /// /// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC TimezoneOffsetPermissive, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot. Nanosecond3NoDot, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot. Nanosecond6NoDot, /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot. 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, 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"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. Numeric(Numeric, Pad), /// Fixed-format item. Fixed(Fixed), /// Issues a formatting error. Used to signal an invalid format string. Error, } const fn num(numeric: Numeric) -> Item<'static> { Item::Numeric(numeric, Pad::None) } const fn num0(numeric: Numeric) -> Item<'static> { Item::Numeric(numeric, Pad::Zero) } const fn nums(numeric: Numeric) -> Item<'static> { Item::Numeric(numeric, Pad::Space) } const fn fixed(fixed: Fixed) -> Item<'static> { Item::Fixed(fixed) } const fn internal_fixed(val: InternalInternal) -> Item<'static> { Item::Fixed(Fixed::Internal(InternalFixed { val })) } /// An error from the `parse` function. #[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 #[allow(clippy::manual_non_exhaustive)] #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, /// There is no possible date and time value with given set of fields. /// /// This does not include the out-of-range conditions, which are trivially invalid. /// It includes the case that there are one or more fields that are inconsistent to each other. Impossible, /// Given set of fields is not enough to make a requested date and time value. /// /// Note that there *may* be a case that given fields constrain the possible values so much /// that there is a unique possible value. Chrono only tries to be correct for /// most useful sets of fields however, as such constraint solving can be expensive. NotEnough, /// The input string has some invalid character sequence for given formatting items. Invalid, /// The input string has been prematurely ended. TooShort, /// All formatting items have been read but there is a remaining input. TooLong, /// There was an error on the formatting string, or there were non-supported formating items. BadFormat, // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. #[doc(hidden)] __Nonexhaustive, } /// Same as `Result`. pub type ParseResult = Result; impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { ParseErrorKind::OutOfRange => write!(f, "input is out of range"), ParseErrorKind::Impossible => write!(f, "no possible date and time matching input"), ParseErrorKind::NotEnough => write!(f, "input is not enough for unique date and time"), ParseErrorKind::Invalid => write!(f, "input contains invalid characters"), 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(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { "parser error, see to_string() for details" } } // to be used in this module and submodules 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); pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); // this implementation is here only because we need some private code from `scan` /// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html). /// /// # Example /// /// ``` /// use chrono::Weekday; /// /// assert_eq!("Sunday".parse::(), Ok(Weekday::Sun)); /// assert!("any day".parse::().is_err()); /// ``` /// /// The parsing is case-insensitive. /// /// ``` /// # use chrono::Weekday; /// assert_eq!("mON".parse::(), 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::().is_err()); /// ``` impl FromStr for Weekday { type Err = ParseWeekdayError; fn from_str(s: &str) -> Result { if let Ok(("", w)) = scan::short_or_long_weekday(s) { Ok(w) } else { Err(ParseWeekdayError { _dummy: () }) } } } /// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html). /// /// # Example /// /// ``` /// use chrono::Month; /// /// assert_eq!("January".parse::(), Ok(Month::January)); /// assert!("any day".parse::().is_err()); /// ``` /// /// The parsing is case-insensitive. /// /// ``` /// # use chrono::Month; /// assert_eq!("fEbruARy".parse::(), 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::().is_err()); /// assert!("Augustin".parse::().is_err()); /// ``` impl FromStr for Month { type Err = ParseMonthError; fn from_str(s: &str) -> Result { if let Ok(("", w)) = scan::short_or_long_month0(s) { match w { 0 => Ok(Month::January), 1 => Ok(Month::February), 2 => Ok(Month::March), 3 => Ok(Month::April), 4 => Ok(Month::May), 5 => Ok(Month::June), 6 => Ok(Month::July), 7 => Ok(Month::August), 8 => Ok(Month::September), 9 => Ok(Month::October), 10 => Ok(Month::November), 11 => Ok(Month::December), _ => Err(ParseMonthError { _dummy: () }), } } else { Err(ParseMonthError { _dummy: () }) } } } chrono-0.4.31/src/format/parse.rs000064400000000000000000002737450072674642500150120ustar 00000000000000// This is a part of Chrono. // Portions copyright (c) 2015, John Nagle. // See README.md and LICENSE.txt for details. //! Date and time parsing routines. 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 crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { 0 => Weekday::Sun, 1 => Weekday::Mon, 2 => Weekday::Tue, 3 => Weekday::Wed, 4 => Weekday::Thu, 5 => Weekday::Fri, 6 => Weekday::Sat, _ => return Err(OUT_OF_RANGE), }) } fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { 1 => Weekday::Mon, 2 => Weekday::Tue, 3 => Weekday::Wed, 4 => Weekday::Thu, 5 => Weekday::Fri, 6 => Weekday::Sat, 7 => Weekday::Sun, _ => return Err(OUT_OF_RANGE), }) } fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ let (s_, v) = $e?; s = s_; v }}; } // an adapted RFC 2822 syntax from Section 3.3 and 4.3: // // c-char = // c-escape = "\" // comment = "(" *(comment / c-char / c-escape) ")" *S // date-time = [ day-of-week "," ] date 1*S time *S *comment // day-of-week = *S day-name *S // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year // day = *S 1*2DIGIT *S // month = 1*S month-name 1*S // month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" // year = *S 2*DIGIT *S // time = time-of-day 1*S zone // time-of-day = hour ":" minute [ ":" second ] // hour = *S 2DIGIT *S // minute = *S 2DIGIT *S // second = *S 2DIGIT *S // zone = ( "+" / "-" ) 4DIGIT / // "UT" / "GMT" / ; same as +0000 // "EST" / "CST" / "MST" / "PST" / ; same as -0500 to -0800 // "EDT" / "CDT" / "MDT" / "PDT" / ; same as -0400 to -0700 // 1*(%d65-90 / %d97-122) ; same as -0000 // // some notes: // // - quoted characters can be in any mixture of lower and upper cases. // // - 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`). 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 // by adding 1900. note that four-or-more-digit years less than 1000 // are *never* affected by this rule. // // - mismatching day-of-week is always an error, which is consistent to // Chrono's own rules. // // - zones can range from `-9959` to `+9959`, but `FixedOffset` does not // support offsets larger than 24 hours. this is not *that* problematic // since we do not directly go to a `DateTime` so one can recover // the offset information from `Parsed` anyway. s = s.trim_start(); if let Ok((s_, weekday)) = scan::short_weekday(s) { if !s_.starts_with(',') { return Err(INVALID); } s = &s_[1..]; parsed.set_weekday(weekday)?; } 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))))?; s = scan::space(s)?; // mandatory // distinguish two- and three-digit years from four-digit years let prevlen = s.len(); let mut year = try_consume!(scan::number(s, 2, usize::MAX)); let yearlen = prevlen - s.len(); match (yearlen, year) { (2, 0..=49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 (2, 50..=99) => { year += 1900; } // 79 -> 1979 (3, _) => { year += 1900; } // 112 -> 2012, 009 -> 1909 (_, _) => {} // 1987 -> 1987, 0654 -> 0654 } parsed.set_year(year)?; s = scan::space(s)?; // mandatory parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; 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_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))?; } // optional comments while let Ok((s_out, ())) = scan::comment_2822(s) { s = s_out; } Ok((s, ())) } 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?; s = s_; v }}; } // an adapted RFC 3339 syntax from Section 5.6: // // date-fullyear = 4DIGIT // date-month = 2DIGIT ; 01-12 // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year // time-hour = 2DIGIT ; 00-23 // time-minute = 2DIGIT ; 00-59 // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules // time-secfrac = "." 1*DIGIT // time-numoffset = ("+" / "-") time-hour ":" time-minute // time-offset = "Z" / time-numoffset // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] // full-date = date-fullyear "-" date-month "-" date-mday // full-time = partial-time time-offset // date-time = full-date "T" full-time // // some notes: // // - quoted characters can be in any mixture of lower and upper cases. // // - it may accept any number of fractional digits for seconds. // for Chrono, this means that we should skip digits past first 9 digits. // // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. // note that this restriction is unique to RFC 3339 and not ISO 8601. // since this is not a typical Chrono behavior, we check it earlier. // // - For readability a full-date and a full-time may be separated by a space character. parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; s = scan::char(s, b'-')?; parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; s = scan::char(s, b'-')?; parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; s = match s.as_bytes().first() { Some(&b't' | &b'T' | &b' ') => &s[1..], Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; s = scan::char(s, b':')?; parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; s = scan::char(s, b':')?; parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; if s.starts_with('.') { let nanosecond = try_consume!(scan::nanosecond(&s[1..])); parsed.set_nanosecond(nanosecond)?; } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); if offset <= -86_400 || offset >= 86_400 { return Err(OUT_OF_RANGE); } parsed.set_offset(i64::from(offset))?; Ok((s, ())) } /// Tries to parse given string into `parsed` with given formatting items. /// Returns `Ok` when the entire string has been parsed (otherwise `parsed` should not be used). /// There should be no trailing string after parsing; /// use a stray [`Item::Space`](./enum.Item.html#variant.Space) to trim whitespaces. /// /// 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 whitespace then any number of zeroes before numbers. /// /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> where I: Iterator, B: Borrow>, { parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) } /// Tries to parse given string into `parsed` with given formatting items. /// Returns `Ok` with a slice of the unparsed remainder. /// /// This particular date and time parser is: /// /// - Greedy. It will consume the longest possible prefix. /// For example, `April` is always consumed entirely when the long month name is requested; /// it equally accepts `Apr`, but prefers the longer prefix in this case. /// /// - Padding-agnostic (for numeric items). /// The [`Pad`](./enum.Pad.html) field is completely ignored, /// so one can prepend any number of zeroes before numbers. /// /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. pub fn parse_and_remainder<'a, 'b, I, B>( parsed: &mut Parsed, s: &'b str, items: I, ) -> ParseResult<&'b str> where I: Iterator, B: Borrow>, { match parse_internal(parsed, s, items) { Ok(s) => Ok(s), Err((s, ParseError(ParseErrorKind::TooLong))) => Ok(s), Err((_s, e)) => Err(e), } } fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, items: I, ) -> Result<&'b str, (&'b str, ParseError)> where I: Iterator, B: Borrow>, { macro_rules! try_consume { ($e:expr) => {{ match $e { Ok((s_, v)) => { s = s_; v } Err(e) => return Err((s, e)), } }}; } for item in items { match *item.borrow() { Item::Literal(prefix) => { if s.len() < prefix.len() { return Err((s, TOO_SHORT)); } if !s.starts_with(prefix) { return Err((s, INVALID)); } s = &s[prefix.len()..]; } #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { return Err((s, TOO_SHORT)); } if !s.starts_with(&prefix[..]) { return Err((s, INVALID)); } s = &s[prefix.len()..]; } Item::Space(_) => { s = s.trim_start(); } #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedSpace(_) => { s = s.trim_start(); } Item::Numeric(ref spec, ref _pad) => { use super::Numeric::*; type Setter = fn(&mut Parsed, i64) -> ParseResult<()>; let (width, signed, set): (usize, bool, Setter) = match *spec { Year => (4, true, Parsed::set_year), YearDiv100 => (2, false, Parsed::set_year_div_100), YearMod100 => (2, false, Parsed::set_year_mod_100), IsoYear => (4, true, Parsed::set_isoyear), IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), Month => (2, false, Parsed::set_month), Day => (2, false, Parsed::set_day), WeekFromSun => (2, false, Parsed::set_week_from_sun), WeekFromMon => (2, false, Parsed::set_week_from_mon), IsoWeek => (2, false, Parsed::set_isoweek), NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday), WeekdayFromMon => (1, false, set_weekday_with_number_from_monday), Ordinal => (3, false, Parsed::set_ordinal), Hour => (2, false, Parsed::set_hour), Hour12 => (2, false, Parsed::set_hour12), Minute => (2, false, Parsed::set_minute), Second => (2, false, Parsed::set_second), Nanosecond => (9, false, Parsed::set_nanosecond), Timestamp => (usize::MAX, false, Parsed::set_timestamp), // for the future expansion Internal(ref int) => match int._dummy {}, }; s = s.trim_start(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))? } else if s.starts_with('+') { try_consume!(scan::number(&s[1..], 1, usize::MAX)) } else { // if there is no explicit sign, we respect the original `width` try_consume!(scan::number(s, 1, width)) } } else { try_consume!(scan::number(s, 1, width)) }; set(parsed, v).map_err(|e| (s, e))?; } Item::Fixed(ref spec) => { use super::Fixed::*; match spec { &ShortMonthName => { let month0 = try_consume!(scan::short_month0(s)); parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; } &LongMonthName => { let month0 = try_consume!(scan::short_or_long_month0(s)); parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; } &ShortWeekdayName => { let weekday = try_consume!(scan::short_weekday(s)); parsed.set_weekday(weekday).map_err(|e| (s, e))?; } &LongWeekdayName => { let weekday = try_consume!(scan::short_or_long_weekday(s)); parsed.set_weekday(weekday).map_err(|e| (s, e))?; } &LowerAmPm | &UpperAmPm => { if s.len() < 2 { return Err((s, TOO_SHORT)); } let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { (b'a', b'm') => false, (b'p', b'm') => true, _ => return Err((s, INVALID)), }; parsed.set_ampm(ampm).map_err(|e| (s, e))?; s = &s[2..]; } &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; } } &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { return Err((s, TOO_SHORT)); } let nano = try_consume!(scan::nanosecond_fixed(s, 3)); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { if s.len() < 6 { return Err((s, TOO_SHORT)); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { if s.len() < 9 { return Err((s, TOO_SHORT)); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; } &TimezoneName => { try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); } &TimezoneOffsetColon | &TimezoneOffsetDoubleColon | &TimezoneOffsetTripleColon | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( 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( 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( 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 => { // Used for the `%+` specifier, which has the description: // "Same as `%Y-%m-%dT%H:%M:%S%.f%:z` (...) // This format also supports having a `Z` or `UTC` in place of `%:z`." // Use the relaxed parser to match this description. try_consume!(parse_rfc3339_relaxed(parsed, s)) } } } Item::Error => { return Err((s, BAD_FORMAT)); } } } // if there are trailling chars, it is an error if !s.is_empty() { Err((s, TOO_LONG)) } else { 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::>()?; /// "2012-12-12 12:12:12Z".parse::>()?; /// "2012- 12-12T12: 12:12Z".parse::>()?; /// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { let mut parsed = Parsed::new(); let (s, _) = parse_rfc3339_relaxed(&mut parsed, s)?; if !s.trim_start().is_empty() { return Err(TOO_LONG); } parsed.to_datetime() } } /// Accepts a relaxed form of RFC3339. /// /// Differences with RFC3339: /// - Values don't require padding to two digits. /// - Years outside the range 0...=9999 are accepted, but they must include a sign. /// - `UTC` is accepted as a valid timezone name/offset (for compatibility with the debug format of /// `DateTime`. /// - There can be spaces between any of the components. /// - The colon in the offset may be missing. fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), Item::Space(""), ]; s = match parse_internal(parsed, s, DATE_ITEMS.iter()) { Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => remainder, Err((_s, e)) => return Err(e), Ok(_) => return Err(NOT_ENOUGH), }; s = match s.as_bytes().first() { Some(&b't' | &b'T' | &b' ') => &s[1..], Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; s = match parse_internal(parsed, s, TIME_ITEMS.iter()) { Err((s, e)) if e.0 == ParseErrorKind::TooLong => s, Err((_s, e)) => return Err(e), Ok(_) => return Err(NOT_ENOUGH), }; 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);)* Ok(expected) }); } #[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")]); } #[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), ); // 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), ); } #[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] 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)); } #[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), ); // 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 ), ); } #[track_caller] fn parses(s: &str, items: &[Item]) { let mut parsed = Parsed::new(); assert!(parse(&mut parsed, s, items.iter()).is_ok()); } #[track_caller] fn check(s: &str, items: &[Item], expected: ParseResult) { let mut parsed = Parsed::new(); let result = parse(&mut parsed, s, items.iter()); let parsed = result.map(|_| parsed); assert_eq!(parsed, expected); } #[test] fn test_rfc2822() { let ymd_hmsn = |y, m, d, h, n, s, nano, off| { FixedOffset::east_opt(off * 60 * 60) .unwrap() .with_ymd_and_hms(y, m, d, h, n, s) .unwrap() .with_nanosecond(nano) .unwrap() }; // Test data - (input, Ok(expected result) or Err(error code)) let testdates = [ ("Tue, 20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case ("Fri, 2 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace ("Fri, 02 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // complex trailing comment (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses ( "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // multiple comments ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment ("20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second ("11 Sep 2001 09:45:00 +0000", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), ("11 Sep 2001 09:45:00 EST", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), ("11 Sep 2001 09:45:00 GMT", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone // 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(NOT_ENOUGH)), ("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(NOT_ENOUGH)), // bad offset: zulu not allowed ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // 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(NOT_ENOUGH)), // bad named timezone ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; parsed.to_datetime() } // Test against test data above for &(date, checkdate) in testdates.iter() { #[cfg(feature = "std")] eprintln!("Test input: {:?}\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] 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(any(feature = "alloc", feature = "std"))] 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() }; // 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::::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))); } } chrono-0.4.31/src/format/parsed.rs000064400000000000000000001613310072674642500151410ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::duration::Duration as OldDuration; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::{DateTime, Datelike, Timelike, Weekday}; /// Parsed parts of date and time. There are two classes of methods: /// /// - `set_*` methods try to set given field(s) while checking for the consistency. /// It may or may not check for the range constraint immediately (for efficiency reasons). /// /// - `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(clippy::manual_non_exhaustive)] #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { /// Year. /// /// This can be negative unlike [`year_div_100`](#structfield.year_div_100) /// and [`year_mod_100`](#structfield.year_mod_100) fields. pub year: Option, /// Year divided by 100. Implies that the year is >= 1 BCE when set. /// /// Due to the common usage, if this field is missing but /// [`year_mod_100`](#structfield.year_mod_100) is present, /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise. pub year_div_100: Option, /// Year modulo 100. Implies that the year is >= 1 BCE when set. pub year_mod_100: Option, /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). /// /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. pub isoyear: Option, /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100. /// Implies that the year is >= 1 BCE when set. /// /// Due to the common usage, if this field is missing but /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present, /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise. pub isoyear_div_100: Option, /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100. /// Implies that the year is >= 1 BCE when set. pub isoyear_mod_100: Option, /// Month (1--12). pub month: Option, /// Week number, where the week 1 starts at the first Sunday of January /// (0--53, 1--53 or 1--52 depending on the year). pub week_from_sun: Option, /// Week number, where the week 1 starts at the first Monday of January /// (0--53, 1--53 or 1--52 depending on the year). pub week_from_mon: Option, /// [ISO week number](../naive/struct.NaiveDate.html#week-date) /// (1--52 or 1--53 depending on the year). pub isoweek: Option, /// Day of the week. pub weekday: Option, /// Day of the year (1--365 or 1--366 depending on the year). pub ordinal: Option, /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). pub day: Option, /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. pub hour_div_12: Option, /// Hour number modulo 12 (0--11). pub hour_mod_12: Option, /// Minute number (0--59). pub minute: Option, /// Second number (0--60, accounting for leap seconds). pub second: Option, /// The number of nanoseconds since the whole second (0--999,999,999). pub nanosecond: Option, /// The number of non-leap seconds since the midnight UTC on January 1, 1970. /// /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second). pub timestamp: Option, /// Offset from the local time to UTC, in seconds. pub offset: Option, /// 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: (), } /// Checks if `old` is either empty or has the same value as `new` (i.e. "consistent"), /// and if it is empty, set `old` to `new` as well. #[inline] fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<()> { if let Some(ref old) = *old { if *old == new { Ok(()) } else { Err(IMPOSSIBLE) } } else { *old = Some(new); Ok(()) } } impl Parsed { /// Returns the initial value of parsed parts. #[must_use] pub fn new() -> Parsed { Parsed::default() } /// Tries to set the [`year`](#structfield.year) field from given value. #[inline] pub fn set_year(&mut self, value: i64) -> ParseResult<()> { 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. #[inline] pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { return Err(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. #[inline] pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { return Err(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, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. #[inline] pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { return Err(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. #[inline] pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { return Err(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, 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, 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, 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, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`weekday`](#structfield.weekday) field from given value. #[inline] pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { set_if_consistent(&mut self.weekday, value) } /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { 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, 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, 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 !(1..=12).contains(&value) { return Err(OUT_OF_RANGE); } set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) } /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { 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(()) } /// 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, 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, 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, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. #[inline] pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) } /// Tries to set the [`offset`](#structfield.offset) field from given value. #[inline] pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Returns a parsed naive date out of given fields. /// /// This method is able to determine the date from given subset of fields: /// /// - Year, month, day. /// - Year, day of the year (ordinal). /// - Year, week number counted from Sunday or Monday, day of the week. /// - ISO week date. /// /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, /// the two-digit year is used to guess the century number then. pub fn to_naive_date(&self) -> ParseResult { fn resolve_year( y: Option, q: Option, r: Option, ) -> ParseResult> { match (y, q, r) { // if there is no further information, simply return the given full year. // this is a common case, so let's avoid division here. (y, None, None) => Ok(y), // if there is a full year *and* also quotient and/or modulo, // 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) => { if y < 0 { return Err(OUT_OF_RANGE); } let q_ = y / 100; let r_ = y % 100; if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { Ok(Some(y)) } else { Err(IMPOSSIBLE) } } // 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)) => { if q < 0 { return Err(OUT_OF_RANGE); } let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); Ok(Some(y.ok_or(OUT_OF_RANGE)?)) } // 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 })), // otherwise it is an out-of-bound or insufficient condition. (None, Some(_), None) => Err(NOT_ENOUGH), (_, _, Some(_)) => Err(OUT_OF_RANGE), } } let given_year = resolve_year(self.year, self.year_div_100, self.year_mod_100)?; let given_isoyear = resolve_year(self.isoyear, self.isoyear_div_100, self.isoyear_mod_100)?; // verify the normal year-month-day date. let verify_ymd = |date: NaiveDate| { let year = date.year(); let (year_div_100, year_mod_100) = if year >= 0 { (Some(year / 100), Some(year % 100)) } else { (None, None) // they should be empty to be consistent }; let month = date.month(); let day = date.day(); self.year.unwrap_or(year) == year && self.year_div_100.or(year_div_100) == year_div_100 && self.year_mod_100.or(year_mod_100) == year_mod_100 && self.month.unwrap_or(month) == month && self.day.unwrap_or(day) == day }; // verify the ISO week date. let verify_isoweekdate = |date: NaiveDate| { let week = date.iso_week(); let isoyear = week.year(); let isoweek = week.week(); let weekday = date.weekday(); let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { (Some(isoyear / 100), Some(isoyear % 100)) } else { (None, None) // they should be empty to be consistent }; self.isoyear.unwrap_or(isoyear) == isoyear && self.isoyear_div_100.or(isoyear_div_100) == isoyear_div_100 && self.isoyear_mod_100.or(isoyear_mod_100) == isoyear_mod_100 && self.isoweek.unwrap_or(isoweek) == isoweek && self.weekday.unwrap_or(weekday) == weekday }; // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); 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 }; // test several possibilities. // tries to construct a full `NaiveDate` as much as possible, then verifies that // it is consistent with other given fields. let (verified, parsed_date) = match (given_year, given_isoyear, self) { (Some(year), _, &Parsed { month: Some(month), day: Some(day), .. }) => { // year, month, day let date = NaiveDate::from_ymd_opt(year, month, day).ok_or(OUT_OF_RANGE)?; (verify_isoweekdate(date) && verify_ordinal(date), date) } (Some(year), _, &Parsed { ordinal: Some(ordinal), .. }) => { // year, day of the year let date = NaiveDate::from_yo_opt(year, ordinal).ok_or(OUT_OF_RANGE)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } ( Some(year), _, &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. }, ) => { // year, week (starting at 1st Sunday), day of the week let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; let firstweek = match newyear.weekday() { Weekday::Sun => 0, Weekday::Mon => 6, Weekday::Tue => 5, Weekday::Wed => 4, Weekday::Thu => 3, Weekday::Fri => 2, Weekday::Sat => 1, }; // `firstweek+1`-th day of January is the beginning of the week 1. if week_from_sun > 53 { return Err(OUT_OF_RANGE); } // can it overflow? let ndays = firstweek + (week_from_sun as i32 - 1) * 7 + weekday.num_days_from_sunday() as i32; let date = newyear .checked_add_signed(OldDuration::days(i64::from(ndays))) .ok_or(OUT_OF_RANGE)?; if date.year() != year { return Err(OUT_OF_RANGE); } // early exit for correct error (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } ( Some(year), _, &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. }, ) => { // year, week (starting at 1st Monday), day of the week let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; let firstweek = match newyear.weekday() { Weekday::Sun => 1, Weekday::Mon => 0, Weekday::Tue => 6, Weekday::Wed => 5, Weekday::Thu => 4, Weekday::Fri => 3, Weekday::Sat => 2, }; // `firstweek+1`-th day of January is the beginning of the week 1. if week_from_mon > 53 { return Err(OUT_OF_RANGE); } // can it overflow? let ndays = firstweek + (week_from_mon as i32 - 1) * 7 + weekday.num_days_from_monday() as i32; let date = newyear .checked_add_signed(OldDuration::days(i64::from(ndays))) .ok_or(OUT_OF_RANGE)?; if date.year() != year { return Err(OUT_OF_RANGE); } // early exit for correct error (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } (_, Some(isoyear), &Parsed { isoweek: Some(isoweek), weekday: Some(weekday), .. }) => { // ISO year, week, day of the week let date = NaiveDate::from_isoywd_opt(isoyear, isoweek, weekday); let date = date.ok_or(OUT_OF_RANGE)?; (verify_ymd(date) && verify_ordinal(date), date) } (_, _, _) => return Err(NOT_ENOUGH), }; if verified { Ok(parsed_date) } else { Err(IMPOSSIBLE) } } /// Returns a parsed naive time out of given fields. /// /// This method is able to determine the time from given subset of fields: /// /// - Hour, minute. (second and nanosecond assumed to be 0) /// - Hour, minute, second. (nanosecond assumed to be 0) /// - Hour, minute, second, nanosecond. /// /// It is able to handle leap seconds when given second is 60. pub fn to_naive_time(&self) -> ParseResult { let hour_div_12 = match self.hour_div_12 { 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(_) => 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(_) => 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), 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(_) => return Err(OUT_OF_RANGE), None => 0, }; NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) } /// Returns a parsed naive date and time out of given fields, /// except for the [`offset`](#structfield.offset) field (assumed to have a given value). /// This is required for parsing a local time or other known-timezone inputs. /// /// This method is able to determine the combined date and time /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field. /// Either way those fields have to be consistent to each other. pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult { let date = self.to_naive_date(); let time = self.to_naive_time(); if let (Ok(date), Ok(time)) = (date, time) { let datetime = date.and_time(time); // verify the timestamp field if any // the following is safe, `timestamp` is very limited in range let timestamp = datetime.timestamp() - i64::from(offset); if let Some(given_timestamp) = self.timestamp { // if `datetime` represents a leap second, it might be off by one second. if given_timestamp != timestamp && !(datetime.nanosecond() >= 1_000_000_000 && given_timestamp == timestamp + 1) { return Err(IMPOSSIBLE); } } Ok(datetime) } else if let Some(timestamp) = self.timestamp { use super::ParseError as PE; use super::ParseErrorKind::{Impossible, OutOfRange}; // if date and time is problematic already, there is no point proceeding. // we at least try to give a correct error though. match (date, time) { (Err(PE(OutOfRange)), _) | (_, Err(PE(OutOfRange))) => return Err(OUT_OF_RANGE), (Err(PE(Impossible)), _) | (_, Err(PE(Impossible))) => return Err(IMPOSSIBLE), (_, _) => {} // one of them is insufficient } // reconstruct date and time fields from timestamp let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; let datetime = NaiveDateTime::from_timestamp_opt(ts, 0); let mut datetime = datetime.ok_or(OUT_OF_RANGE)?; // fill year, ordinal, hour, minute and second fields from timestamp. // if existing fields are consistent, this will allow the full date/time reconstruction. let mut parsed = self.clone(); if parsed.second == Some(60) { // `datetime.second()` cannot be 60, so this is the only case for a leap second. match datetime.second() { // it's okay, just do not try to overwrite the existing field. 59 => {} // `datetime` is known to be off by one second. 0 => { datetime -= OldDuration::seconds(1); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), } // ...and we have the correct candidates for other fields. } else { parsed.set_second(i64::from(datetime.second()))?; } parsed.set_year(i64::from(datetime.year()))?; parsed.set_ordinal(i64::from(datetime.ordinal()))?; // more efficient than ymd parsed.set_hour(i64::from(datetime.hour()))?; parsed.set_minute(i64::from(datetime.minute()))?; // validate other fields (e.g. week) and return let date = parsed.to_naive_date()?; let time = parsed.to_naive_time()?; Ok(date.and_time(time)) } else { // reproduce the previous error(s) date?; time?; unreachable!() } } /// Returns a parsed fixed time zone offset out of given fields. pub fn to_fixed_offset(&self) -> ParseResult { self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) } /// Returns a parsed timezone-aware date and time out of given fields. /// /// This method is able to determine the combined date and time /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, /// plus a time zone offset. /// Either way those fields have to be consistent to each other. pub fn to_datetime(&self) -> ParseResult> { // 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)?; // this is used to prevent an overflow when calling FixedOffset::from_local_datetime datetime .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) .ok_or(OUT_OF_RANGE)?; match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), } } /// Returns a parsed timezone-aware date and time out of given fields, /// with an additional `TimeZone` used to interpret and validate the local date. /// /// This method is able to determine the combined date and time /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, /// plus a time zone offset. /// Either way those fields have to be consistent to each other. /// If parsed fields include an UTC offset, it also has to be consistent to /// [`offset`](#structfield.offset). pub fn to_datetime_with_timezone(&self, tz: &Tz) -> ParseResult> { // if we have `timestamp` specified, guess an offset from that. let mut guessed_offset = 0; if let Some(timestamp) = self.timestamp { // make a naive `DateTime` from given timestamp and (if any) nanosecond. // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. let nanosecond = self.nanosecond.unwrap_or(0); let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond); let dt = dt.ok_or(OUT_OF_RANGE)?; guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); } // checks if the given `DateTime` has a consistent `Offset` with given `self.offset`. let check_offset = |dt: &DateTime| { if let Some(offset) = self.offset { dt.offset().fix().local_minus_utc() == offset } else { true } }; // `guessed_offset` should be correct when `self.timestamp` is given. // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; match tz.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => { if check_offset(&t) { Ok(t) } else { Err(IMPOSSIBLE) } } LocalResult::Ambiguous(min, max) => { // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), (false, true) => Ok(max), (true, false) => Ok(min), (true, true) => Err(NOT_ENOUGH), } } } } } #[cfg(test)] mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Datelike; use crate::Weekday::*; #[test] fn test_parsed_set_fields() { // year*, isoyear* let mut p = Parsed::new(); assert_eq!(p.set_year(1987), Ok(())); assert_eq!(p.set_year(1986), Err(IMPOSSIBLE)); assert_eq!(p.set_year(1988), Err(IMPOSSIBLE)); assert_eq!(p.set_year(1987), Ok(())); assert_eq!(p.set_year_div_100(20), Ok(())); // independent to `year` assert_eq!(p.set_year_div_100(21), Err(IMPOSSIBLE)); assert_eq!(p.set_year_div_100(19), Err(IMPOSSIBLE)); assert_eq!(p.set_year_mod_100(37), Ok(())); // ditto assert_eq!(p.set_year_mod_100(38), Err(IMPOSSIBLE)); assert_eq!(p.set_year_mod_100(36), Err(IMPOSSIBLE)); let mut p = Parsed::new(); assert_eq!(p.set_year(0), Ok(())); assert_eq!(p.set_year_div_100(0), Ok(())); assert_eq!(p.set_year_mod_100(0), Ok(())); let mut p = Parsed::new(); assert_eq!(p.set_year_div_100(-1), Err(OUT_OF_RANGE)); assert_eq!(p.set_year_mod_100(-1), Err(OUT_OF_RANGE)); assert_eq!(p.set_year(-1), Ok(())); assert_eq!(p.set_year(-2), Err(IMPOSSIBLE)); assert_eq!(p.set_year(0), Err(IMPOSSIBLE)); let mut p = Parsed::new(); assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); assert_eq!(p.set_year_div_100(8), Ok(())); assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); // month, week*, isoweek, ordinal, day, minute, second, nanosecond, offset let mut p = Parsed::new(); assert_eq!(p.set_month(7), Ok(())); assert_eq!(p.set_month(1), Err(IMPOSSIBLE)); assert_eq!(p.set_month(6), Err(IMPOSSIBLE)); assert_eq!(p.set_month(8), Err(IMPOSSIBLE)); assert_eq!(p.set_month(12), Err(IMPOSSIBLE)); let mut p = Parsed::new(); assert_eq!(p.set_month(8), Ok(())); assert_eq!(p.set_month(0x1_0000_0008), Err(OUT_OF_RANGE)); // hour let mut p = Parsed::new(); assert_eq!(p.set_hour(12), Ok(())); assert_eq!(p.set_hour(11), Err(IMPOSSIBLE)); assert_eq!(p.set_hour(13), Err(IMPOSSIBLE)); assert_eq!(p.set_hour(12), Ok(())); assert_eq!(p.set_ampm(false), Err(IMPOSSIBLE)); assert_eq!(p.set_ampm(true), Ok(())); assert_eq!(p.set_hour12(12), Ok(())); assert_eq!(p.set_hour12(0), Err(OUT_OF_RANGE)); // requires canonical representation assert_eq!(p.set_hour12(1), Err(IMPOSSIBLE)); assert_eq!(p.set_hour12(11), Err(IMPOSSIBLE)); let mut p = Parsed::new(); assert_eq!(p.set_ampm(true), Ok(())); assert_eq!(p.set_hour12(7), Ok(())); assert_eq!(p.set_hour(7), Err(IMPOSSIBLE)); assert_eq!(p.set_hour(18), Err(IMPOSSIBLE)); assert_eq!(p.set_hour(19), Ok(())); // timestamp let mut p = Parsed::new(); assert_eq!(p.set_timestamp(1_234_567_890), Ok(())); assert_eq!(p.set_timestamp(1_234_567_889), Err(IMPOSSIBLE)); assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); } #[test] fn test_parsed_to_naive_date() { macro_rules! parse { ($($k:ident: $v:expr),*) => ( Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_date() ) } let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); // ymd: omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 1984), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 1984, month: 1), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 1984, month: 1, day: 2), ymd(1984, 1, 2)); assert_eq!(parse!(year: 1984, day: 2), Err(NOT_ENOUGH)); assert_eq!(parse!(year_div_100: 19), Err(NOT_ENOUGH)); assert_eq!(parse!(year_div_100: 19, year_mod_100: 84), Err(NOT_ENOUGH)); assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1), Err(NOT_ENOUGH)); assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1, day: 2), ymd(1984, 1, 2)); assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, day: 2), Err(NOT_ENOUGH)); assert_eq!(parse!(year_div_100: 19, month: 1, day: 2), Err(NOT_ENOUGH)); assert_eq!(parse!(year_mod_100: 70, month: 1, day: 2), ymd(1970, 1, 2)); assert_eq!(parse!(year_mod_100: 69, month: 1, day: 2), ymd(2069, 1, 2)); // ymd: out-of-range conditions assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 2, day: 29), ymd(1984, 2, 29)); assert_eq!( parse!(year_div_100: 19, year_mod_100: 83, month: 2, day: 29), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year_div_100: 19, year_mod_100: 83, month: 13, day: 1), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 31), ymd(1983, 12, 31) ); assert_eq!( parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 32), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 0), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year_div_100: 19, year_mod_100: 100, month: 1, day: 1), Err(OUT_OF_RANGE) ); 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 = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, year_mod_100: max_year % 100, month: 1, day: 1), ymd(max_year, 1, 1) ); assert_eq!( parse!(year_div_100: (max_year + 1) / 100, year_mod_100: (max_year + 1) % 100, month: 1, day: 1), Err(OUT_OF_RANGE) ); // ymd: conflicting inputs assert_eq!(parse!(year: 1984, year_div_100: 19, month: 1, day: 1), ymd(1984, 1, 1)); assert_eq!(parse!(year: 1984, year_div_100: 20, month: 1, day: 1), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 1984, year_mod_100: 84, month: 1, day: 1), ymd(1984, 1, 1)); assert_eq!(parse!(year: 1984, year_mod_100: 83, month: 1, day: 1), Err(IMPOSSIBLE)); assert_eq!( parse!(year: 1984, year_div_100: 19, year_mod_100: 84, month: 1, day: 1), ymd(1984, 1, 1) ); assert_eq!( parse!(year: 1984, year_div_100: 18, year_mod_100: 94, month: 1, day: 1), Err(IMPOSSIBLE) ); assert_eq!( parse!(year: 1984, year_div_100: 18, year_mod_100: 184, month: 1, day: 1), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year: -1, year_div_100: 0, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE) ); assert_eq!( parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE) ); assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE)); // weekdates assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sun), ymd(2000, 1, 2)); assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Mon), ymd(2000, 1, 3)); assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Mon), ymd(2000, 1, 3)); assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sat), ymd(2000, 1, 8)); assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sat), ymd(2000, 1, 8)); assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sun), ymd(2000, 1, 9)); assert_eq!(parse!(year: 2000, week_from_sun: 2, weekday: Sun), ymd(2000, 1, 9)); assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); // weekdates: conflicting inputs assert_eq!( parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sat), ymd(2000, 1, 8) ); assert_eq!( parse!(year: 2000, week_from_mon: 1, week_from_sun: 2, weekday: Sun), ymd(2000, 1, 9) ); assert_eq!( parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sun), Err(IMPOSSIBLE) ); assert_eq!( parse!(year: 2000, week_from_mon: 2, week_from_sun: 2, weekday: Sun), Err(IMPOSSIBLE) ); // ISO weekdates assert_eq!(parse!(isoyear: 2004, isoweek: 53), Err(NOT_ENOUGH)); assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Fri), ymd(2004, 12, 31)); assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Sat), ymd(2005, 1, 1)); assert_eq!(parse!(isoyear: 2004, isoweek: 0xffffffff, weekday: Sat), Err(OUT_OF_RANGE)); assert_eq!(parse!(isoyear: 2005, isoweek: 0, weekday: Thu), Err(OUT_OF_RANGE)); assert_eq!(parse!(isoyear: 2005, isoweek: 5, weekday: Thu), ymd(2005, 2, 3)); assert_eq!(parse!(isoyear: 2005, weekday: Thu), Err(NOT_ENOUGH)); // year and ordinal assert_eq!(parse!(ordinal: 123), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, ordinal: 0), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2000, ordinal: 1), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, ordinal: 60), ymd(2000, 2, 29)); assert_eq!(parse!(year: 2000, ordinal: 61), ymd(2000, 3, 1)); assert_eq!(parse!(year: 2000, ordinal: 366), ymd(2000, 12, 31)); assert_eq!(parse!(year: 2000, ordinal: 367), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2000, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2100, ordinal: 0), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2100, ordinal: 1), ymd(2100, 1, 1)); assert_eq!(parse!(year: 2100, ordinal: 59), ymd(2100, 2, 28)); assert_eq!(parse!(year: 2100, ordinal: 60), ymd(2100, 3, 1)); assert_eq!(parse!(year: 2100, ordinal: 365), ymd(2100, 12, 31)); assert_eq!(parse!(year: 2100, ordinal: 366), Err(OUT_OF_RANGE)); assert_eq!(parse!(year: 2100, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); // more complex cases assert_eq!( parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed), ymd(2014, 12, 31) ); assert_eq!( parse!(year: 2014, month: 12, ordinal: 365, isoyear: 2015, isoweek: 1, week_from_sun: 52, week_from_mon: 52), ymd(2014, 12, 31) ); assert_eq!( parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2014, isoweek: 53, week_from_sun: 52, week_from_mon: 52, weekday: Wed), Err(IMPOSSIBLE) ); // no ISO week date 2014-W53-3 assert_eq!( parse!(year: 2012, isoyear: 2015, isoweek: 1, week_from_sun: 52, week_from_mon: 52), Err(NOT_ENOUGH) ); // ambiguous (2014-12-29, 2014-12-30, 2014-12-31) assert_eq!(parse!(year_div_100: 20, isoyear_mod_100: 15, ordinal: 366), Err(NOT_ENOUGH)); // technically unique (2014-12-31) but Chrono gives up } #[test] fn test_parsed_to_naive_time() { macro_rules! parse { ($($k:ident: $v:expr),*) => ( Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_time() ) } 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)); assert_eq!(parse!(hour_div_12: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1), Err(NOT_ENOUGH)); assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23), hms(1, 23, 0)); assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45), hms(1, 23, 45)); assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45, nanosecond: 678_901_234), hmsn(1, 23, 45, 678_901_234) ); assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6), hms(23, 45, 6)); assert_eq!(parse!(hour_mod_12: 1, minute: 23), Err(NOT_ENOUGH)); assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, nanosecond: 456_789_012), Err(NOT_ENOUGH) ); // out-of-range conditions assert_eq!(parse!(hour_div_12: 2, hour_mod_12: 0, minute: 0), Err(OUT_OF_RANGE)); assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 12, minute: 0), Err(OUT_OF_RANGE)); assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 60), Err(OUT_OF_RANGE)); assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 61), Err(OUT_OF_RANGE) ); assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 34, nanosecond: 1_000_000_000), Err(OUT_OF_RANGE) ); // leap seconds assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60), hmsn(1, 23, 59, 1_000_000_000) ); assert_eq!( parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60, nanosecond: 999_999_999), hmsn(1, 23, 59, 1_999_999_999) ); } #[test] fn test_parsed_to_naive_datetime_with_offset() { macro_rules! parse { (offset = $offset:expr; $($k:ident: $v:expr),*) => ( Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_datetime_with_offset($offset) ); ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) } 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)); assert_eq!( parse!(year: 2015, month: 1, day: 30, hour_div_12: 1, hour_mod_12: 2, minute: 38), ymdhms(2015, 1, 30, 14, 38, 0) ); assert_eq!( parse!(year: 1997, month: 1, day: 30, hour_div_12: 1, hour_mod_12: 2, minute: 38, second: 5), ymdhms(1997, 1, 30, 14, 38, 5) ); assert_eq!( parse!(year: 2012, ordinal: 34, hour_div_12: 0, hour_mod_12: 5, minute: 6, second: 7, nanosecond: 890_123_456), ymdhmsn(2012, 2, 3, 5, 6, 7, 890_123_456) ); assert_eq!(parse!(timestamp: 0), ymdhms(1970, 1, 1, 0, 0, 0)); assert_eq!(parse!(timestamp: 1, nanosecond: 0), ymdhms(1970, 1, 1, 0, 0, 1)); assert_eq!(parse!(timestamp: 1, nanosecond: 1), ymdhmsn(1970, 1, 1, 0, 0, 1, 1)); assert_eq!(parse!(timestamp: 1_420_000_000), ymdhms(2014, 12, 31, 4, 26, 40)); assert_eq!(parse!(timestamp: -0x1_0000_0000), ymdhms(1833, 11, 24, 17, 31, 44)); // full fields assert_eq!( parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, timestamp: 1_420_000_000), ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) ); assert_eq!( parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, timestamp: 1_419_999_999), Err(IMPOSSIBLE) ); assert_eq!( parse!(offset = 32400; year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, timestamp: 1_419_967_600), ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) ); // more timestamps let max_days_from_year_1970 = 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 = 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(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: year_0_from_year_1970.num_seconds()), ymdhms(0, 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); // leap seconds #1: partial fields assert_eq!(parse!(second: 59, timestamp: 1_341_100_798), Err(IMPOSSIBLE)); assert_eq!(parse!(second: 59, timestamp: 1_341_100_799), ymdhms(2012, 6, 30, 23, 59, 59)); assert_eq!(parse!(second: 59, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); assert_eq!( parse!(second: 60, timestamp: 1_341_100_799), ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) ); assert_eq!( parse!(second: 60, timestamp: 1_341_100_800), ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) ); assert_eq!(parse!(second: 0, timestamp: 1_341_100_800), ymdhms(2012, 7, 1, 0, 0, 0)); assert_eq!(parse!(second: 1, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); assert_eq!(parse!(second: 60, timestamp: 1_341_100_801), Err(IMPOSSIBLE)); // leap seconds #2: full fields // we need to have separate tests for them since it uses another control flow. assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 59, timestamp: 1_341_100_798), Err(IMPOSSIBLE) ); assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 59, timestamp: 1_341_100_799), ymdhms(2012, 6, 30, 23, 59, 59) ); assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 59, timestamp: 1_341_100_800), Err(IMPOSSIBLE) ); assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 60, timestamp: 1_341_100_799), ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) ); assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 60, timestamp: 1_341_100_800), ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) ); assert_eq!( parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, minute: 0, second: 0, timestamp: 1_341_100_800), ymdhms(2012, 7, 1, 0, 0, 0) ); assert_eq!( parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, minute: 0, second: 1, timestamp: 1_341_100_800), Err(IMPOSSIBLE) ); assert_eq!( parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, minute: 59, second: 60, timestamp: 1_341_100_801), Err(IMPOSSIBLE) ); // error codes assert_eq!( parse!(year: 2015, month: 1, day: 20, weekday: Tue, hour_div_12: 2, hour_mod_12: 1, minute: 35, second: 20), Err(OUT_OF_RANGE) ); // `hour_div_12` is out of range } #[test] fn test_parsed_to_datetime() { macro_rules! parse { ($($k:ident: $v:expr),*) => ( Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime() ) } let ymdhmsn = |y, m, d, h, n, s, nano, off| { 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)); assert_eq!( parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678), Err(NOT_ENOUGH) ); assert_eq!( parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678, 0) ); assert_eq!( parse!(year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), ymdhmsn(2014, 12, 31, 13, 26, 40, 12_345_678, 32400) ); assert_eq!( parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 1, minute: 42, second: 4, nanosecond: 12_345_678, offset: -9876), ymdhmsn(2014, 12, 31, 1, 42, 4, 12_345_678, -9876) ); assert_eq!( parse!(year: 2015, ordinal: 1, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 86_400), Err(OUT_OF_RANGE) ); // `FixedOffset` does not support such huge offset } #[test] fn test_parsed_to_datetime_with_timezone() { macro_rules! parse { ($tz:expr; $($k:ident: $v:expr),*) => ( Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime_with_timezone(&$tz) ) } // single result from ymdhms assert_eq!( 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 .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; year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), Err(IMPOSSIBLE) ); assert_eq!( 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_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_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.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_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( 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()); } } chrono-0.4.31/src/format/scan.rs000064400000000000000000000343700072674642500146110ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. /*! * Various scanning routines for the parser. */ use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; /// Tries to parse the non-negative number from `min` to `max` digits. /// /// The absence of digits at all is an unconditional error. /// 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(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 // the first non-numeric byte, which may be another ascii character or beginning of multi-byte // UTF-8 character. let bytes = s.as_bytes(); if bytes.len() < min { return Err(TOO_SHORT); } let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() if !c.is_ascii_digit() { if i < min { return Err(INVALID); } else { return Ok((&s[i..], n)); } } n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) { Some(n) => n, None => return Err(OUT_OF_RANGE), }; } 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(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)?; let consumed = origlen - s.len(); // scale the number accordingly. static SCALE: [i64; 10] = [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1]; let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. let s = s.trim_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(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)?; // scale the number accordingly. static SCALE: [i64; 10] = [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1]; let v = v.checked_mul(SCALE[digits]).ok_or(OUT_OF_RANGE)?; Ok((s, v)) } /// Tries to parse the month index (0 through 11) with the first three ASCII letters. pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } let buf = s.as_bytes(); let month0 = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) { (b'j', b'a', b'n') => 0, (b'f', b'e', b'b') => 1, (b'm', b'a', b'r') => 2, (b'a', b'p', b'r') => 3, (b'm', b'a', b'y') => 4, (b'j', b'u', b'n') => 5, (b'j', b'u', b'l') => 6, (b'a', b'u', b'g') => 7, (b's', b'e', b'p') => 8, (b'o', b'c', b't') => 9, (b'n', b'o', b'v') => 10, (b'd', b'e', b'c') => 11, _ => return Err(INVALID), }; Ok((&s[3..], month0)) } /// Tries to parse the weekday with the first three ASCII letters. pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { if s.len() < 3 { return Err(TOO_SHORT); } let buf = s.as_bytes(); let weekday = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) { (b'm', b'o', b'n') => Weekday::Mon, (b't', b'u', b'e') => Weekday::Tue, (b'w', b'e', b'd') => Weekday::Wed, (b't', b'h', b'u') => Weekday::Thu, (b'f', b'r', b'i') => Weekday::Fri, (b's', b'a', b't') => Weekday::Sat, (b's', b'u', b'n') => Weekday::Sun, _ => return Err(INVALID), }; Ok((&s[3..], 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(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars 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() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } Ok((s, month0)) } /// 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(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars 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() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } Ok((s, weekday)) } /// Tries to consume exactly one given character. 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), None => Err(TOO_SHORT), } } /// Tries to consume one or more whitespace. 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() { Err(TOO_SHORT) } else { Err(INVALID) } } /// Consumes any number (including zero) of colon or spaces. pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } /// Parse a timezone from `s` and return the offset in seconds. /// /// The `consume_colon` function is used to parse a mandatory or optional `:` /// separator between hours offset and minutes offset. /// /// The `allow_missing_minutes` flag allows the timezone minutes offset to be /// missing from `s`. /// /// The `allow_tz_minus_sign` flag allows the timezone offset negative character /// to also be `−` MINUS SIGN (U+2212) in addition to the typical /// ASCII-compatible `-` HYPHEN-MINUS (U+2D). /// This is part of [RFC 3339 & ISO 8601]. /// /// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, allow_zulu: bool, allow_missing_minutes: bool, allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { 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) } else { Ok((b[0], b[1])) } } let negative = match s.chars().next() { Some('+') => { // PLUS SIGN (U+2B) s = &s['+'.len_utf8()..]; false } Some('-') => { // HYPHEN-MINUS (U+2D) s = &s['-'.len_utf8()..]; true } Some('−') => { // MINUS SIGN (U+2212) if !allow_tz_minus_sign { return Err(INVALID); } s = &s['−'.len_utf8()..]; true } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; // 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')), _ => return Err(INVALID), }; s = &s[2..]; // colons (and possibly other separators) s = consume_colon(s)?; // minutes (00--59) // 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), _ => return Err(INVALID), } } else if allow_missing_minutes { 0 } else { return Err(TOO_SHORT); }; s = match s.len() { len if len >= 2 => &s[2..], len if len == 0 => s, _ => return Err(TOO_SHORT), }; let seconds = hours * 3600 + minutes * 60; Ok((s, if negative { -seconds } else { seconds })) } /// 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, Option)> { // 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, Some(o * 3600))); if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") { offset_hours(0) } else if name.eq_ignore_ascii_case(b"edt") { offset_hours(-4) } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { offset_hours(-5) } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { offset_hours(-6) } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { offset_hours(-7) } else if name.eq_ignore_ascii_case(b"pst") { offset_hours(-8) } else if name.len() == 1 { match name[0] { // recommended by RFC 2822: consume but treat it as -0000 b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), _ => Ok((s, None)), } } else { Ok((s, None)) } } else { let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?; Ok((s_, Some(offset))) } } /// 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) } 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 ); } } #[test] fn test_timezone_offset_2822() { assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", Some(-21600))); assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", Some(-28800))); assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", Some(-25200))); assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", Some(-57060))); assert_eq!(timezone_offset_2822("Gp").unwrap(), ("", None)); } #[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)); } } chrono-0.4.31/src/format/strftime.rs000064400000000000000000001307120072674642500155170ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. /*! `strftime`/`strptime`-inspired date and time formatting syntax. ## Specifiers 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. 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. | | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. | | `%h` | `Jul` | Same as `%b`. | | | | | | `%d` | `08` | Day number (01--31), zero-padded to 2 digits. | | `%e` | ` 8` | Same as `%d` but space-padded. Same as `%_d`. | | | | | | `%a` | `Sun` | Abbreviated weekday name. Always 3 letters. | | `%A` | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in 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. [^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. [^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. | | | | | | `%D` | `07/08/01` | Month-day-year format. Same as `%m/%d/%y`. | | `%x` | `07/08/01` | Locale's date representation (e.g., 12/31/99). | | `%F` | `2001-07-08` | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`. | | `%v` | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`. | | | | | | | | **TIME SPECIFIERS:** | | `%H` | `00` | Hour number (00--23), zero-padded to 2 digits. | | `%k` | ` 0` | Same as `%H` but space-padded. Same as `%_H`. | | `%I` | `12` | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. | | `%l` | `12` | Same as `%I` but space-padded. Same as `%_I`. | | | | | | `%P` | `am` | `am` or `pm` in 12-hour clocks. | | `%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. [^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` | 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. 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. [^5] | | | | | | `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]| | | | | | | | **SPECIAL SPECIFIERS:** | | `%t` | | Literal tab (`\t`). | | `%n` | | Literal newline (`\n`). | | `%%` | | Literal percent sign. | It is possible to override the default padding behavior of numeric specifiers `%?`. This is not allowed for other specifiers and will result in the `BAD_FORMAT` error. Modifier | Description -------- | ----------- `%-?` | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`) `%_?` | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`) `%0?` | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`) Notes: [^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. [^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. [^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`. [^4]: `%S`: It accounts for leap seconds, so `60` is possible. [^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional digits for seconds and colons in the time zone offset.

This format also supports having a `Z` or `UTC` in place of `%:z`. They are equivalent to `+00:00`.

Note that all `T`, `Z`, and `UTC` are parsed case-insensitively.

The typical `strftime` implementations have different (and locale-dependent) formats for this specifier. While Chrono's format for `%+` is far more stable, it is best to avoid this specifier if you want to control the exact output. [^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. [^7]: `%f`, `%.f`:
`%f` and `%.f` are notably different formatting specifiers.
`%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a second.
Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`. [^8]: `%Z`: Since `chrono` is not aware of timezones beyond their offsets, this specifier **only prints the offset** when used for formatting. The timezone abbreviation will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960) for more information.

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.

It is not possible to reliably convert from an abbreviation to an offset, for example CDT can mean either Central Daylight Time (North America) or China Daylight Time. */ use super::{fixed, internal_fixed, num, num0, nums}; #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalInternal, Item, Numeric, Pad}; /// Parsing iterator for `strftime`-like format strings. #[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. `%+`), /// `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, } impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[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. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } } const HAVE_ALTERNATES: &str = "z"; impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; fn next(&mut self) -> Option> { // We have items queued to return from a specifier composed of multiple formatting items. if let Some((item, remainder)) = self.queue.split_first() { self.queue = remainder; return Some(item.clone()); } // We are in the middle of parsing the localized formatting string of a specifier. #[cfg(feature = "unstable-locales")] if !self.locale_str.is_empty() { let (remainder, item) = self.parse_next_item(self.locale_str)?; self.locale_str = remainder; return Some(item); } // Normal: we are parsing the formatting string. let (remainder, item) = self.parse_next_item(self.remainder)?; self.remainder = remainder; Some(item) } } 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('%') => { remainder = &remainder[1..]; macro_rules! next { () => { match remainder.chars().next() { Some(x) => { remainder = &remainder[x.len_utf8()..]; x } None => return Some((remainder, Item::Error)), // premature end of string } }; } let spec = next!(); let pad_override = match spec { '-' => Some(Pad::None), '0' => Some(Pad::Zero), '_' => Some(Pad::Space), _ => None, }; let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { return Some((remainder, Item::Error)); } macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ const QUEUE: &'static [Item<'static>] = &[$($tail),+]; self.queue = QUEUE; $head }) } #[cfg(not(feature = "unstable-locales"))] macro_rules! queue_from_slice { ($slice:expr) => {{ self.queue = &$slice[1..]; $slice[0].clone() }}; } let item = match spec { 'A' => fixed(Fixed::LongWeekdayName), 'B' => fixed(Fixed::LongMonthName), 'C' => num0(YearDiv100), 'D' => { queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] } 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], 'G' => num0(IsoYear), 'H' => num0(Hour), 'I' => num0(Hour12), 'M' => num0(Minute), 'P' => fixed(Fixed::LowerAmPm), 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], 'S' => num0(Second), 'T' => { queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] } 'U' => num0(WeekFromSun), 'V' => num0(IsoWeek), 'W' => num0(WeekFromMon), #[cfg(not(feature = "unstable-locales"))] 'X' => queue_from_slice!(T_FMT), #[cfg(feature = "unstable-locales")] 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), 'Y' => num0(Year), 'Z' => fixed(Fixed::TimezoneName), 'a' => fixed(Fixed::ShortWeekdayName), 'b' | 'h' => fixed(Fixed::ShortMonthName), #[cfg(not(feature = "unstable-locales"))] 'c' => queue_from_slice!(D_T_FMT), #[cfg(feature = "unstable-locales")] 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), 'd' => num0(Day), 'e' => nums(Day), 'f' => num0(Nanosecond), 'g' => num0(IsoYearMod100), 'j' => num0(Ordinal), 'k' => nums(Hour), 'l' => nums(Hour12), 'm' => num0(Month), 'n' => Space("\n"), 'p' => fixed(Fixed::UpperAmPm), #[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' => { queue![ nums(Day), Literal("-"), fixed(Fixed::ShortMonthName), Literal("-"), num0(Year) ] } 'w' => num(NumDaysFromSun), #[cfg(not(feature = "unstable-locales"))] 'x' => queue_from_slice!(D_FMT), #[cfg(feature = "unstable-locales")] 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), 'y' => num0(YearMod100), 'z' => { if is_alternate { internal_fixed(TimezoneOffsetPermissive) } else { 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 } } '.' => match next!() { '3' => match next!() { 'f' => fixed(Fixed::Nanosecond3), _ => Item::Error, }, '6' => match next!() { 'f' => fixed(Fixed::Nanosecond6), _ => Item::Error, }, '9' => match next!() { 'f' => fixed(Fixed::Nanosecond9), _ => Item::Error, }, 'f' => fixed(Fixed::Nanosecond), _ => Item::Error, }, '3' => match next!() { 'f' => internal_fixed(Nanosecond3NoDot), _ => Item::Error, }, '6' => match next!() { 'f' => internal_fixed(Nanosecond6NoDot), _ => Item::Error, }, '9' => match next!() { 'f' => internal_fixed(Nanosecond9NoDot), _ => Item::Error, }, '%' => Literal("%"), _ => Item::Error, // no such specifier }; // Adjust `item` if we have any padding modifier. // Not allowed on non-numeric items or on specifiers composed out of multiple // formatting items. if let Some(new_pad) = pad_override { match item { Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { Some((remainder, Item::Numeric(kind.clone(), new_pad))) } _ => Some((remainder, Item::Error)), } } else { Some((remainder, item)) } } // the next item is space Some(c) if c.is_whitespace() => { // `%` is not a whitespace, so `c != '%'` is redundant let nextspec = remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); let item = Space(&remainder[..nextspec]); remainder = &remainder[nextspec..]; Some((remainder, item)) } // the next item is literal _ => { let nextspec = remainder .find(|c: char| c.is_whitespace() || c == '%') .unwrap_or(remainder.len()); assert!(nextspec > 0); let item = Literal(&remainder[..nextspec]); remainder = &remainder[nextspec..]; 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)] 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(any(feature = "alloc", feature = "std"))] use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] fn test_strftime_items() { fn parse_and_collect(s: &str) -> Vec> { // map any error into `[Item::Error]`. useful for easy testing. eprintln!("test_strftime_items: parse_and_collect({:?})", s); let items = StrftimeItems::new(s); let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); items.collect::>>().unwrap_or_else(|| vec![Item::Error]) } assert_eq!(parse_and_collect(""), []); assert_eq!(parse_and_collect(" "), [Space(" ")]); assert_eq!(parse_and_collect(" "), [Space(" ")]); // ne! assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]); // eq! assert_eq!(parse_and_collect(" "), [Space(" ")]); assert_eq!(parse_and_collect("a"), [Literal("a")]); assert_eq!(parse_and_collect("ab"), [Literal("ab")]); assert_eq!(parse_and_collect("😽"), [Literal("😽")]); assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]); assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]); assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); // ne! assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]); assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]); assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]); // eq! assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); assert_eq!( parse_and_collect("a b\t\nc"), [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")] ); assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]); assert_eq!( parse_and_collect("100%% ok"), [Literal("100"), Literal("%"), Space(" "), Literal("ok")] ); assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]); assert_eq!( parse_and_collect("%Y-%m-%d"), [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] ); assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]); assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]); assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]); assert_eq!( parse_and_collect("😽😽a b😽c"), [Literal("😽😽a"), Space(" "), Literal("b😽c")] ); assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]); assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]); assert_eq!( parse_and_collect(" 😽 😽"), [Space(" "), Literal("😽"), Space(" "), Literal("😽")] ); assert_eq!( parse_and_collect(" 😽 😽 "), [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] ); assert_eq!( parse_and_collect(" 😽 😽 "), [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] ); assert_eq!( parse_and_collect(" 😽 😽😽 "), [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] ); assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]); assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); assert_eq!( parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")] ); assert_eq!( parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")] ); assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); assert_eq!( parse_and_collect(" 😽 😽😽 "), [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] ); assert_eq!( parse_and_collect(" 😽 😽はい😽 ハンバーガー"), [ Space(" "), Literal("😽"), Space(" "), Literal("😽はい😽"), Space(" "), Literal("ハンバーガー") ] ); assert_eq!( parse_and_collect("%%😽%%😽"), [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")] ); assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]); assert_eq!( parse_and_collect("100%%😽%%a"), [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")] ); assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]); assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); assert_eq!(parse_and_collect("%%"), [Literal("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]); assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]); assert_eq!(parse_and_collect("%%a%"), [Item::Error]); assert_eq!(parse_and_collect("%😽"), [Item::Error]); assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); assert_eq!( parse_and_collect("%%%%ハンバーガー"), [Literal("%"), Literal("%"), Literal("ハンバーガー")] ); assert_eq!(parse_and_collect("foo%?"), [Item::Error]); assert_eq!(parse_and_collect("bar%42"), [Item::Error]); assert_eq!(parse_and_collect("quux% +"), [Item::Error]); assert_eq!(parse_and_collect("%.Z"), [Item::Error]); assert_eq!(parse_and_collect("%:Z"), [Item::Error]); assert_eq!(parse_and_collect("%-Z"), [Item::Error]); assert_eq!(parse_and_collect("%0Z"), [Item::Error]); assert_eq!(parse_and_collect("%_Z"), [Item::Error]); assert_eq!(parse_and_collect("%.j"), [Item::Error]); assert_eq!(parse_and_collect("%:j"), [Item::Error]); assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]); assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]); assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]); assert_eq!(parse_and_collect("%.e"), [Item::Error]); assert_eq!(parse_and_collect("%:e"), [Item::Error]); assert_eq!(parse_and_collect("%-e"), [num(Day)]); assert_eq!(parse_and_collect("%0e"), [num0(Day)]); assert_eq!(parse_and_collect("%_e"), [nums(Day)]); assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]); assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]); assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]); assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]); assert_eq!( parse_and_collect("%#z"), [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] ); assert_eq!(parse_and_collect("%#m"), [Item::Error]); } #[test] #[cfg(any(feature = "alloc", feature = "std"))] fn test_strftime_docs() { let dt = FixedOffset::east_opt(34200) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2001, 7, 8) .unwrap() .and_hms_nano_opt(0, 34, 59, 1_026_490_708) .unwrap(), ) .unwrap(); // date specifiers assert_eq!(dt.format("%Y").to_string(), "2001"); assert_eq!(dt.format("%C").to_string(), "20"); assert_eq!(dt.format("%y").to_string(), "01"); assert_eq!(dt.format("%m").to_string(), "07"); assert_eq!(dt.format("%b").to_string(), "Jul"); assert_eq!(dt.format("%B").to_string(), "July"); assert_eq!(dt.format("%h").to_string(), "Jul"); assert_eq!(dt.format("%d").to_string(), "08"); assert_eq!(dt.format("%e").to_string(), " 8"); assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); assert_eq!(dt.format("%a").to_string(), "Sun"); assert_eq!(dt.format("%A").to_string(), "Sunday"); assert_eq!(dt.format("%w").to_string(), "0"); assert_eq!(dt.format("%u").to_string(), "7"); assert_eq!(dt.format("%U").to_string(), "27"); assert_eq!(dt.format("%W").to_string(), "27"); assert_eq!(dt.format("%G").to_string(), "2001"); assert_eq!(dt.format("%g").to_string(), "01"); assert_eq!(dt.format("%V").to_string(), "27"); assert_eq!(dt.format("%j").to_string(), "189"); assert_eq!(dt.format("%D").to_string(), "07/08/01"); assert_eq!(dt.format("%x").to_string(), "07/08/01"); assert_eq!(dt.format("%F").to_string(), "2001-07-08"); assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); // time specifiers assert_eq!(dt.format("%H").to_string(), "00"); assert_eq!(dt.format("%k").to_string(), " 0"); assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); assert_eq!(dt.format("%I").to_string(), "12"); assert_eq!(dt.format("%l").to_string(), "12"); assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); assert_eq!(dt.format("%P").to_string(), "am"); assert_eq!(dt.format("%p").to_string(), "AM"); assert_eq!(dt.format("%M").to_string(), "34"); assert_eq!(dt.format("%S").to_string(), "60"); assert_eq!(dt.format("%f").to_string(), "026490708"); assert_eq!(dt.format("%.f").to_string(), ".026490708"); assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); assert_eq!(dt.format("%.3f").to_string(), ".026"); assert_eq!(dt.format("%.6f").to_string(), ".026490"); assert_eq!(dt.format("%.9f").to_string(), ".026490708"); assert_eq!(dt.format("%3f").to_string(), "026"); assert_eq!(dt.format("%6f").to_string(), "026490"); assert_eq!(dt.format("%9f").to_string(), "026490708"); assert_eq!(dt.format("%R").to_string(), "00:34"); assert_eq!(dt.format("%T").to_string(), "00:34:60"); assert_eq!(dt.format("%X").to_string(), "00:34:60"); assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); // time zone specifiers //assert_eq!(dt.format("%Z").to_string(), "ACST"); assert_eq!(dt.format("%z").to_string(), "+0930"); assert_eq!(dt.format("%:z").to_string(), "+09:30"); assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); assert_eq!(dt.format("%:::z").to_string(), "+09"); // date & time specifiers assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); assert_eq!( dt.with_timezone(&Utc).format("%+").to_string(), "2001-07-07T15:04:60.026490708+00:00" ); assert_eq!( dt.with_timezone(&Utc), DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() ); assert_eq!( dt.with_timezone(&Utc), DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() ); assert_eq!( dt.with_timezone(&Utc), DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() ); assert_eq!( dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), "2001-07-08T00:34:60.026490+09:30" ); assert_eq!(dt.format("%s").to_string(), "994518299"); // special specifiers assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); // complex format specifiers assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); assert_eq!( dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), " 20010807%%\t00:am:3460+09\t" ); } #[test] #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] fn test_strftime_docs_localized() { let dt = FixedOffset::east_opt(34200) .unwrap() .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) .unwrap() .with_nanosecond(1_026_490_708) .unwrap(); // date specifiers assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); // time specifiers assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60"); // date & time specifiers assert_eq!( dt.format_localized("%c", Locale::fr_BE).to_string(), "dim 08 jui 2001 00:34:60 +09:30" ); let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); // date specifiers assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); } /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does /// not cause a panic. /// /// See . #[test] #[cfg(any(feature = "alloc", feature = "std"))] 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", any(feature = "alloc", feature = "std")))] 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", any(feature = "alloc", feature = "std")))] 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", target_pointer_width = "64"))] fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 2); } #[test] #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))] fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 12); assert_eq!(size_of::(), 28); assert_eq!(size_of::(), 2); } } chrono-0.4.31/src/lib.rs000064400000000000000000000710570072674642500131460ustar 00000000000000//! # Chrono: Date and Time for Rust //! //! 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): //! //! * 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. //! //! 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 //! //! Chrono supports various runtime environments and operating systems, and has //! several features that may be enabled or disabled. //! //! Default features: //! //! - `alloc`: Enable features that depend on allocation (primarily string formatting) //! - `std`: Enables functionality that depends on the standard library. This //! is a superset of `alloc` and adds interoperation with standard library types //! and traits. //! - `clock`: Enables reading the system time (`now`) 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: //! //! - [`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 //! //! 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. //! //! 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`**](./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`**](./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`**](./offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. //! //! * [**`Local`**](./offset/struct.Local.html) specifies the system local time zone. //! //! * [**`FixedOffset`**](./offset/struct.FixedOffset.html) specifies //! an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. //! This often results from the parsed textual date and time. //! Since it stores the most information and does not depend on the system environment, //! you would want to normalize other `TimeZone`s into this type. //! //! `DateTime`s with different `TimeZone` types are distinct and do not mix, //! but can be converted to each other using //! the [`DateTime::with_timezone`](./struct.DateTime.html#method.with_timezone) method. //! //! You can get the current date and time in the UTC time zone //! ([`Utc::now()`](./offset/struct.Utc.html#method.now)) //! or in the local time zone //! ([`Local::now()`](./offset/struct.Local.html#method.now)). //! #![cfg_attr(not(feature = "clock"), doc = "```ignore")] #![cfg_attr(feature = "clock", doc = "```rust")] //! use chrono::prelude::*; //! //! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` //! # let _ = utc; 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. //! #![cfg_attr(not(feature = "std"), doc = "```ignore")] #![cfg_attr(feature = "std", doc = "```rust")] //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! //! # 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, 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, NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc()); //! //! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_milli_opt(9, 10, 11, 12)?.and_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.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); //! //! // 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.from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap()).unwrap(); //! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; //! # Some(()) //! # } //! # doctest().unwrap(); //! ``` //! //! Various properties are available to the date and time, and can be altered individually. //! Most of them are defined in the traits [`Datelike`](./trait.Datelike.html) and //! [`Timelike`](./trait.Timelike.html) which you should `use` before. //! 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_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)); //! 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_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 //! 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.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), Duration::seconds(-2 * 3600 + 2)); //! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); //! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + Duration::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() - Duration::seconds(1_000_000_000), //! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); //! ``` //! //! ### Formatting and Parsing //! //! Formatting is done via the [`format`](./struct.DateTime.html#method.format) method, //! which format is equivalent to the familiar `strftime` format. //! //! See [`format::strftime`](./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`](./struct.DateTime.html#method.to_rfc2822) and //! [`to_rfc3339`](./struct.DateTime.html#method.to_rfc3339) methods //! for well-known formats. //! //! Chrono now also provides date formatting in almost any language without the //! help of an additional C library. This functionality is under the feature //! `unstable-locales`: //! //! ```toml //! chrono = { version = "0.4", features = ["unstable-locales"] } //! ``` //! //! The `unstable-locales` feature requires and implies at least the `alloc` feature. //! //! ```rust //! # #[allow(unused_imports)] //! use chrono::prelude::*; //! //! # #[cfg(feature = "unstable-locales")] //! # 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.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 = 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(feature = "unstable-locales"))] //! # fn test() {} //! # if cfg!(feature = "unstable-locales") { //! # test(); //! # } //! ``` //! //! Parsing can be done with three methods: //! //! 1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait //! (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method //! on a string) can be used for parsing `DateTime`, `DateTime` and //! `DateTime` values. This parses what the `{:?}` //! ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) //! format specifier prints, and requires the offset to be present. //! //! 2. [`DateTime::parse_from_str`](./struct.DateTime.html#method.parse_from_str) parses //! a date and time with offsets and returns `DateTime`. //! This should be used when the offset is a part of input and the caller cannot guess that. //! It *cannot* be used when the offset can be missing. //! [`DateTime::parse_from_rfc2822`](./struct.DateTime.html#method.parse_from_rfc2822) //! and //! [`DateTime::parse_from_rfc3339`](./struct.DateTime.html#method.parse_from_rfc3339) //! are similar but for well-known formats. //! //! 3. [`Offset::datetime_from_str`](./offset/trait.TimeZone.html#method.datetime_from_str) is //! similar but returns `DateTime` of given offset. //! When the explicit offset is missing from the input, it simply uses given offset. //! It issues an error when the input contains an explicit offset different //! from the current offset. //! //! More detailed control over the parsing process is available via //! [`format`](./format/index.html) module. //! //! ```rust //! use chrono::prelude::*; //! //! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9*3600).unwrap()); //! //! // method 1 //! assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(dt.clone())); //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); //! //! // method 2 //! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), //! Ok(fixed_dt.clone())); //! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), //! Ok(fixed_dt.clone())); //! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! //! // oops, the year is missing! //! 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!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); //! // oops, the weekday is incorrect! //! assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); //! ``` //! //! Again : See [`format::strftime`](./format/strftime/index.html#specifiers) //! documentation for full syntax and list of specifiers. //! //! ### Conversion from and to EPOCH timestamps //! //! Use [`DateTime::from_timestamp(seconds, nanoseconds)`](DateTime::from_timestamp) //! to construct a [`DateTime`] from a UNIX timestamp //! (seconds, nanoseconds that passed since January 1st 1970). //! //! Use [`DateTime.timestamp`](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. //! #![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, Utc}; //! //! // Construct a datetime from epoch: //! let dt: DateTime = 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: //! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); //! assert_eq!(dt.timestamp(), 1_500_000_000); //! ``` //! //! ### Naive date and time //! //! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` //! as [**`NaiveDate`**](./naive/struct.NaiveDate.html), //! [**`NaiveTime`**](./naive/struct.NaiveTime.html) and //! [**`NaiveDateTime`**](./naive/struct.NaiveDateTime.html) respectively. //! //! They have almost equivalent interfaces as their timezone-aware twins, //! but are not associated to time zones obviously and can be quite low-level. //! They are mostly useful for building blocks for higher-level types. //! //! Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: //! [`naive_local`](./struct.DateTime.html#method.naive_local) returns //! a view to the naive local time, //! and [`naive_utc`](./struct.DateTime.html#method.naive_utc) returns //! a view to the naive UTC time. //! //! ## Limitations //! //! 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.57.0**. //! //! 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_opt(2014, 1, 30).unwrap().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. //! //! ## 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)] #![warn(unreachable_pub)] #![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] // 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_cfg))] #[cfg(feature = "alloc")] extern crate alloc; mod duration; pub use duration::Duration; #[cfg(feature = "std")] pub use duration::OutOfRangeError; use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { #[doc(no_inline)] #[allow(deprecated)] pub use crate::Date; #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use crate::Local; #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[doc(no_inline)] pub use crate::Locale; #[doc(no_inline)] pub use crate::SubsecRound; #[doc(no_inline)] pub use crate::{DateTime, SecondsFormat}; #[doc(no_inline)] pub use crate::{Datelike, Month, Timelike, Weekday}; #[doc(no_inline)] pub use crate::{FixedOffset, Utc}; #[doc(no_inline)] pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; #[doc(no_inline)] pub use crate::{Offset, TimeZone}; } mod date; #[allow(deprecated)] pub use date::{Date, MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] pub use datetime::rustc_serialize::TsSeconds; #[allow(deprecated)] pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub use format::Locale; pub use format::{ParseError, ParseResult}; pub mod naive; #[doc(no_inline)] pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use offset::Local; #[doc(no_inline)] pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; mod weekday; pub use weekday::{ParseWeekdayError, Weekday}; mod month; pub use month::{Month, Months, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; #[cfg(feature = "__internal_bench")] #[doc(hidden)] pub use naive::__BenchYearFlags; /// 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]. /// /// *Available on crate feature 'serde' only.* /// /// [1]: https://tools.ietf.org/html/rfc3339 /// [2]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } /// Out of range error type used in various converting APIs #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct OutOfRange { _private: (), } impl OutOfRange { const fn new() -> OutOfRange { OutOfRange { _private: () } } } impl fmt::Display for OutOfRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "out of range") } } impl fmt::Debug for OutOfRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "out of range") } } #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} /// Workaround because `?` is not (yet) available in const context. #[macro_export] macro_rules! try_opt { ($e:expr) => { match $e { Some(v) => v, None => return None, } }; } /// Workaround because `.expect()` is not (yet) available in const context. #[macro_export] macro_rules! expect { ($e:expr, $m:literal) => { match $e { Some(v) => v, None => panic!($m), } }; } chrono-0.4.31/src/month.rs000064400000000000000000000312550072674642500135210ustar 00000000000000use core::fmt; #[cfg(feature = "rkyv")] 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(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", 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 for Month { type Error = OutOfRange; fn try_from(value: u8) -> Result { match value { 1 => Ok(Month::January), 2 => Ok(Month::February), 3 => Ok(Month::March), 4 => Ok(Month::April), 5 => Ok(Month::May), 6 => Ok(Month::June), 7 => Ok(Month::July), 8 => Ok(Month::August), 9 => Ok(Month::September), 10 => Ok(Month::October), 11 => Ok(Month::November), 12 => Ok(Month::December), _ => Err(OutOfRange::new()), } } } impl num_traits::FromPrimitive for Month { /// Returns an `Option` from a i64, assuming a 1-index, January = 1. /// /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` /// ---------------------------| -------------------- | --------------------- | ... | ----- /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) #[inline] fn from_u64(n: u64) -> Option { Self::from_u32(n as u32) } #[inline] fn from_i64(n: i64) -> Option { Self::from_u32(n as u32) } #[inline] fn from_u32(n: u32) -> Option { match n { 1 => Some(Month::January), 2 => Some(Month::February), 3 => Some(Month::March), 4 => Some(Month::April), 5 => Some(Month::May), 6 => Some(Month::June), 7 => Some(Month::July), 8 => Some(Month::August), 9 => Some(Month::September), 10 => Some(Month::October), 11 => Some(Month::November), 12 => Some(Month::December), _ => None, } } } /// A duration in calendar months #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[cfg_attr(feature = "arbitrary", 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) } } /// An error resulting from reading `` value with `FromStr`. #[derive(Clone, PartialEq, Eq)] pub struct ParseMonthError { pub(crate) _dummy: (), } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(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")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod month_serde { use super::Month; use serde::{de, ser}; use core::fmt; impl ser::Serialize for Month { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.collect_str(self.name()) } } struct MonthVisitor; impl<'de> de::Visitor<'de> for MonthVisitor { type Value = Month; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Month") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) } } impl<'de> de::Deserialize<'de> for Month { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(MonthVisitor) } } } #[cfg(test)] mod tests { use super::Month; use crate::{Datelike, 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] #[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::(string).unwrap(); assert_eq!(month, expected_month); } let errors: Vec<&str> = vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; for string in errors { from_str::(string).unwrap_err(); } } } chrono-0.4.31/src/naive/date.rs000064400000000000000000003565450072674642500144270ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date without timezone. #[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; /// L10n locales. #[cfg(feature = "unstable-locales")] use pure_rust_locales::Locale; use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] 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, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; const MAX_YEAR: i32 = internals::MAX_YEAR; const MIN_YEAR: i32 = internals::MIN_YEAR; /// 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 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 }; self.date.add_days(days).unwrap() } /// 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 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 }; self.date.add_days(days).unwrap() } /// Returns a [`RangeInclusive`] representing the whole week bounded by /// [first_day](./struct.NaiveWeek.html#method.first_day) and /// [last_day](./struct.NaiveWeek.html#method.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 fn days(&self) -> RangeInclusive { self.first_day()..=self.last_day() } } /// A duration in calendar days. /// /// This is useful because when using `Duration` it is possible /// that adding `Duration::days(1)` doesn't increment the day value as expected due to it being a /// fixed number of seconds. This difference applies only when dealing with `DateTime` data types /// and in other cases `Duration::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] from Jan 1, 262145 BCE to Dec 31, 262143 CE. /// Also supports the conversion from ISO 8601 ordinal and week date. /// /// # Calendar Date /// /// The ISO 8601 **calendar date** follows the proleptic Gregorian calendar. /// It is like a normal civil calendar but note some slight differences: /// /// * Dates before the Gregorian calendar's inception in 1582 are defined via the extrapolation. /// Be careful, as historical dates are often noted in the Julian calendar and others /// and the transition to Gregorian may differ across countries (as late as early 20C). /// /// (Some example: Both Shakespeare from Britain and Cervantes from Spain seemingly died /// on the same calendar date---April 23, 1616---but in the different calendar. /// Britain used the Julian calendar at that time, so Shakespeare's death is later.) /// /// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). /// If you need a typical BCE/BC and CE/AD notation for year numbers, /// use the [`Datelike::year_ce`](../trait.Datelike.html#method.year_ce) method. /// /// # Week Date /// /// The ISO 8601 **week date** is a triple of year number, week number /// and [day of the week](../enum.Weekday.html) with the following rules: /// /// * A week consists of Monday through Sunday, and is always numbered within some year. /// The week number ranges from 1 to 52 or 53 depending on the year. /// /// * The week 1 of given year is defined as the first week containing January 4 of that year, /// or equivalently, the first week containing four or more days in that year. /// /// * The year number in the week date may *not* correspond to the actual Gregorian year. /// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. /// /// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), /// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and /// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods /// can be used to get the corresponding week date. /// /// # Ordinal Date /// /// The ISO 8601 **ordinal date** is a pair of year number and day of the year ("ordinal"). /// The ordinal number ranges from 1 to 365 or 366 depending on the year. /// The year number is the same as that of the [calendar date](#calendar-date). /// /// This is currently the internal format of Chrono's date types. /// /// [proleptic Gregorian date]: crate::NaiveDate#calendar-date #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveDate { ymdf: DateImpl, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). #[deprecated(since = "0.4.20", note = "Use NaiveDate::MIN instead")] pub const MIN_DATE: NaiveDate = NaiveDate::MIN; /// The maximum possible `NaiveDate` (December 31, 262143 CE). #[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] pub const MAX_DATE: NaiveDate = NaiveDate::MAX; #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NaiveDate { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; let max_days = YearFlags::from_year(year).ndays(); let ord = u.int_in_range(1..=max_days)?; NaiveDate::from_yo_opt(year, ord).ok_or(arbitrary::Error::IncorrectFormat) } } impl NaiveDate { pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { (self.ordinal() as i32 - self.weekday().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 { 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. /// Does not check whether the flags are correct for the provided year. const fn from_mdf(year: i32, mdf: Mdf) -> Option { if year < MIN_YEAR || year > MAX_YEAR { return None; // Out-of-range } 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 /// /// Panics if the specified calendar day does not exist, on invalid values for `month` or `day`, /// or if `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] #[must_use] pub const fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { expect!(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) /// (year, month and day). /// /// # Errors /// /// Returns `None` if: /// - The specified calendar day does not exist (for example 2023-04-31). /// - The value for `month` or `day` is invalid. /// - `year` is out of range for `NaiveDate`. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let from_ymd_opt = NaiveDate::from_ymd_opt; /// /// assert!(from_ymd_opt(2015, 3, 14).is_some()); /// assert!(from_ymd_opt(2015, 0, 14).is_none()); /// assert!(from_ymd_opt(2015, 2, 29).is_none()); /// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year /// assert!(from_ymd_opt(400000, 1, 1).is_none()); /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); /// ``` #[must_use] pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { let flags = YearFlags::from_year(year); if let Some(mdf) = Mdf::new(month, day, flags) { NaiveDate::from_mdf(year, mdf) } else { None } } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) /// (year and day of the year). /// /// # Panics /// /// Panics if the specified ordinal day does not exist, on invalid values for `ordinal`, or if /// `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] #[must_use] pub const fn from_yo(year: i32, ordinal: u32) -> NaiveDate { expect!(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) /// (year and day of the year). /// /// # Errors /// /// Returns `None` if: /// - The specified ordinal day does not exist (for example 2023-366). /// - The value for `ordinal` is invalid (for example: `0`, `400`). /// - `year` is out of range for `NaiveDate`. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let from_yo_opt = NaiveDate::from_yo_opt; /// /// assert!(from_yo_opt(2015, 100).is_some()); /// assert!(from_yo_opt(2015, 0).is_none()); /// assert!(from_yo_opt(2015, 365).is_some()); /// assert!(from_yo_opt(2015, 366).is_none()); /// assert!(from_yo_opt(-4, 366).is_some()); // 5 BCE is a leap year /// assert!(from_yo_opt(400000, 1).is_none()); /// assert!(from_yo_opt(-400000, 1).is_none()); /// ``` #[must_use] pub const fn from_yo_opt(year: i32, ordinal: u32) -> Option { let flags = YearFlags::from_year(year); NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) /// (year, week number and day of the week). /// The resulting `NaiveDate` may have a different year from the input year. /// /// # Panics /// /// Panics if the specified week does not exist in that year, on invalid values for `week`, or /// if the resulting date is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] #[must_use] pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { expect!(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) /// (year, week number and day of the week). /// The resulting `NaiveDate` may have a different year from the input year. /// /// # Errors /// /// Returns `None` if: /// - The specified week does not exist in that year (for example 2023 week 53). /// - The value for `week` is invalid (for example: `0`, `60`). /// - If the resulting date is out of range for `NaiveDate`. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Weekday}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; /// /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); /// assert_eq!(from_isoywd_opt(2015, 10, Weekday::Sun), Some(from_ymd(2015, 3, 8))); /// assert_eq!(from_isoywd_opt(2015, 30, Weekday::Mon), Some(from_ymd(2015, 7, 20))); /// assert_eq!(from_isoywd_opt(2015, 60, Weekday::Mon), None); /// /// assert_eq!(from_isoywd_opt(400000, 10, Weekday::Fri), None); /// assert_eq!(from_isoywd_opt(-400000, 10, Weekday::Sat), None); /// ``` /// /// The year number of ISO week date may differ from that of the calendar date. /// /// ``` /// # use chrono::{NaiveDate, Weekday}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; /// // Mo Tu We Th Fr Sa Su /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, /// // 2015-W01 29 30 31 1 2 3 4 <- so this is the first week /// assert_eq!(from_isoywd_opt(2014, 52, Weekday::Sun), Some(from_ymd(2014, 12, 28))); /// assert_eq!(from_isoywd_opt(2014, 53, Weekday::Mon), None); /// assert_eq!(from_isoywd_opt(2015, 1, Weekday::Mon), Some(from_ymd(2014, 12, 29))); /// /// // 2015-W52 21 22 23 24 25 26 27 has 4+ days of old year, /// // 2015-W53 28 29 30 31 1 2 3 <- so this is the last week /// // 2016-W01 4 5 6 7 8 9 10 /// assert_eq!(from_isoywd_opt(2015, 52, Weekday::Sun), Some(from_ymd(2015, 12, 27))); /// assert_eq!(from_isoywd_opt(2015, 53, Weekday::Sun), Some(from_ymd(2016, 1, 3))); /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); /// ``` #[must_use] pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { let flags = YearFlags::from_year(year); let nweeks = flags.nisoweeks(); if 1 <= week && week <= nweeks { // ordinal = week ordinal - delta let weekord = week * 7 + weekday as u32; let delta = flags.isoweek_delta(); if weekord <= delta { // ordinal < 1, previous year let prevflags = YearFlags::from_year(year - 1); NaiveDate::from_ordinal_and_flags( year - 1, weekord + prevflags.ndays() - delta, prevflags, ) } else { let ordinal = weekord - delta; let ndays = flags.ndays(); if ordinal <= ndays { // this year NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } else { // ordinal > ndays, next year let nextflags = YearFlags::from_year(year + 1); NaiveDate::from_ordinal_and_flags(year + 1, ordinal - ndays, nextflags) } } } else { None } } /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with /// January 1, 1 being day 1. /// /// # Panics /// /// Panics if the date is out of range. #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] #[inline] #[must_use] pub const fn from_num_days_from_ce(days: i32) -> NaiveDate { expect!(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") } /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with /// January 1, 1 being day 1. /// /// # Errors /// /// Returns `None` if the date is out of range. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); /// assert_eq!(from_ndays_opt(100_000_000), None); /// assert_eq!(from_ndays_opt(-100_000_000), None); /// ``` #[must_use] pub const fn from_num_days_from_ce_opt(days: i32) -> Option { let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 let year_div_400 = days.div_euclid(146_097); let cycle = days.rem_euclid(146_097); let (year_mod_400, ordinal) = 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 `NaiveDate` by counting the number of occurrences of a particular day-of-week /// since the beginning of the given month. For instance, if you want the 2nd Friday of March /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. /// /// `n` is 1-indexed. /// /// # Panics /// /// Panics if the specified day does not exist in that month, on invalid values for `month` or /// `n`, or if `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")] #[must_use] pub const fn from_weekday_of_month( year: i32, month: u32, weekday: Weekday, n: u8, ) -> NaiveDate { expect!(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week /// since the beginning of the given month. For instance, if you want the 2nd Friday of March /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. /// /// `n` is 1-indexed. /// /// # Errors /// /// Returns `None` if: /// - The specified day does not exist in that month (for example the 5th Monday of Apr. 2023). /// - The value for `month` or `n` is invalid. /// - `year` is out of range for `NaiveDate`. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Weekday}; /// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), /// NaiveDate::from_ymd_opt(2017, 3, 10)) /// ``` #[must_use] pub const fn from_weekday_of_month_opt( year: i32, month: u32, weekday: Weekday, n: u8, ) -> Option { if n == 0 { return None; } let first = try_opt!(NaiveDate::from_ymd_opt(year, month, 1)).weekday(); let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; let day = (n - 1) as u32 * 7 + first_to_dow + 1; NaiveDate::from_ymd_opt(year, month, day) } /// Parses a string with the specified format string and returns a new `NaiveDate`. /// See the [`format::strftime` module](../format/strftime/index.html) /// on the supported escape sequences. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let parse_from_str = NaiveDate::parse_from_str; /// /// assert_eq!(parse_from_str("2015-09-05", "%Y-%m-%d"), /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); /// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"), /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); /// ``` /// /// Time and offset is ignored for the purpose of parsing. /// /// ``` /// # use chrono::NaiveDate; /// # let parse_from_str = NaiveDate::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap())); /// ``` /// /// Out-of-bound dates or insufficient fields are errors. /// /// ``` /// # use chrono::NaiveDate; /// # let parse_from_str = NaiveDate::parse_from_str; /// assert!(parse_from_str("2015/9", "%Y/%m").is_err()); /// assert!(parse_from_str("2015/9/31", "%Y/%m/%d").is_err()); /// ``` /// /// All parsed fields should be consistent to each other, otherwise it's an error. /// /// ``` /// # use chrono::NaiveDate; /// # let parse_from_str = NaiveDate::parse_from_str; /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_naive_date() } /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with /// the remaining portion of the string. /// See the [`format::strftime` module](../format/strftime/index.html) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). /// /// # Example /// /// ```rust /// # use chrono::{NaiveDate}; /// let (date, remainder) = NaiveDate::parse_and_remainder( /// "2015-02-18 trailing text", "%Y-%m-%d").unwrap(); /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); /// assert_eq!(remainder, " trailing text"); /// ``` pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDate, &'a str)> { let mut parsed = Parsed::new(); let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_naive_date().map(|d| (d, remainder)) } /// Add a duration in [`Months`] to the date /// /// Uses the last day of the month if the day does not exist in the resulting month. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// # use chrono::{NaiveDate, Months}; /// assert_eq!( /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_months(Months::new(6)), /// Some(NaiveDate::from_ymd_opt(2022, 8, 20).unwrap()) /// ); /// assert_eq!( /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_months(Months::new(2)), /// Some(NaiveDate::from_ymd_opt(2022, 9, 30).unwrap()) /// ); /// ``` #[must_use] pub const fn checked_add_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); } match months.0 <= 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 { 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 { 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 { match days.0 <= i32::MAX as u64 { true => self.add_days(days.0 as i32), false => None, } } /// Subtract a duration in [`Days`] from the date /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) /// ); /// assert_eq!( /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)), /// None /// ); /// ``` #[must_use] pub const fn checked_sub_days(self, days: Days) -> Option { match days.0 <= i32::MAX as u64 { true => self.add_days(-(days.0 as i32)), false => None, } } /// Add a duration of `i32` days to the date. pub(crate) const fn add_days(self, days: i32) -> Option { // fast path if the result is within the same year 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_opt(2015, 6, 3).unwrap(); /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); /// /// let dt: NaiveDateTime = d.and_time(t); /// assert_eq!(dt.date(), d); /// assert_eq!(dt.time(), t); /// ``` #[inline] #[must_use] pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { NaiveDateTime::new(*self, time) } /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. /// /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. /// /// # Panics /// /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] #[must_use] pub const fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { expect!(self.and_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. /// /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. /// /// # Errors /// /// Returns `None` on invalid hour, minute and/or second. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_opt(12, 34, 56).is_some()); /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead /// assert!(d.and_hms_opt(12, 60, 56).is_none()); /// assert!(d.and_hms_opt(24, 34, 56).is_none()); /// ``` #[inline] #[must_use] pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { let time = try_opt!(NaiveTime::from_hms_opt(hour, min, sec)); Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// /// The millisecond part is allowed to exceed 1,000,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 millisecond. #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] #[must_use] pub const fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { expect!(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// /// The millisecond part is allowed to exceed 1,000,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_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); /// ``` #[inline] #[must_use] pub const fn and_hms_milli_opt( &self, hour: u32, min: u32, sec: u32, milli: u32, ) -> Option { let time = try_opt!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli)); Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// /// The microsecond part is allowed to exceed 1,000,000,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_opt(2015, 6, 3).unwrap(); /// /// let dt: NaiveDateTime = d.and_hms_micro_opt(12, 34, 56, 789_012).unwrap(); /// assert_eq!(dt.year(), 2015); /// assert_eq!(dt.weekday(), Weekday::Wed); /// assert_eq!(dt.second(), 56); /// assert_eq!(dt.nanosecond(), 789_012_000); /// ``` #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] #[inline] #[must_use] pub const fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { expect!(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute, second and/or microsecond. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); /// ``` #[inline] #[must_use] pub const fn and_hms_micro_opt( &self, hour: u32, min: u32, sec: u32, micro: u32, ) -> Option { let time = try_opt!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro)); Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] #[inline] #[must_use] pub const fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { expect!(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); /// ``` #[inline] #[must_use] pub const fn and_hms_nano_opt( &self, hour: u32, min: u32, sec: u32, nano: u32, ) -> Option { let time = try_opt!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano)); Some(self.and_time(time)) } /// Returns the packed month-day-flags. #[inline] const fn mdf(&self) -> Mdf { self.of().to_mdf() } /// Returns the packed ordinal-flags. #[inline] 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] const fn with_mdf(&self, mdf: Mdf) -> Option { 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] 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 /// /// Panics when `self` is the last representable date. #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] #[must_use] pub const fn succ(&self) -> NaiveDate { expect!(self.succ_opt(), "out of bound") } /// Makes a new `NaiveDate` for the next calendar date. /// /// # Errors /// /// Returns `None` when `self` is the last representable date. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap())); /// assert_eq!(NaiveDate::MAX.succ_opt(), None); /// ``` #[inline] #[must_use] pub const fn succ_opt(&self) -> Option { 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 /// /// Panics when `self` is the first representable date. #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] #[must_use] pub const fn pred(&self) -> NaiveDate { expect!(self.pred_opt(), "out of bound") } /// Makes a new `NaiveDate` for the previous calendar date. /// /// # Errors /// /// Returns `None` when `self` is the first representable date. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap())); /// assert_eq!(NaiveDate::MIN.pred_opt(), None); /// ``` #[inline] #[must_use] pub const fn pred_opt(&self) -> Option { match self.of().pred() { Some(of) => Some(self.with_of(of)), None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), } } /// Adds the number of whole days in the given `Duration` to the current date. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// use chrono::{Duration, NaiveDate}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.checked_add_signed(Duration::days(40)), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); /// assert_eq!(d.checked_add_signed(Duration::days(-40)), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); /// 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!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let days = i32::try_from(rhs.num_days()).ok()?; self.add_days(days) } /// Subtracts the number of whole days in the given `Duration` from the current date. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// use chrono::{Duration, NaiveDate}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.checked_sub_signed(Duration::days(40)), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); /// assert_eq!(d.checked_sub_signed(Duration::days(-40)), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); /// 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!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let days = i32::try_from(-rhs.num_days()).ok()?; self.add_days(days) } /// Subtracts another `NaiveDate` from the current date. /// Returns a `Duration` of integral numbers. /// /// This does not overflow or underflow at all, /// as all possible output fits in the range of `Duration`. /// /// # Example /// /// ``` /// use chrono::{Duration, NaiveDate}; /// /// 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)); /// ``` #[must_use] pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { let year1 = self.year(); let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); let cycle1 = 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; OldDuration::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 { let mut years = self.year() - base.year(); // Comparing tuples is not (yet) possible in const context. Instead we combine month and // day into one `u32` for easy comparison. if (self.month() << 5 | self.day()) < (base.month() << 5 | base.day()) { years -= 1; } match years >= 0 { true => Some(years as u32), false => None, } } /// Formats the date with the specified formatting items. /// Otherwise it is the same as the ordinary `format` method. /// /// The `Iterator` of items should be `Clone`able, /// since the resulting `DelayedFormat` value may be formatted multiple times. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// use chrono::format::strftime::StrftimeItems; /// /// let fmt = StrftimeItems::new("%Y-%m-%d"); /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. /// /// ``` /// # use chrono::NaiveDate; /// # use chrono::format::strftime::StrftimeItems; /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new(Some(*self), None, items) } /// Formats the date with the specified format string. /// See the [`format::strftime` module](../format/strftime/index.html) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, /// which gets converted to a string only when actual formatting happens. /// You may use the `to_string` method to get a `String`, /// or just feed it into `print!` and other formatting macros. /// (In this way it avoids the redundant memory allocation.) /// /// A wrong format string does *not* issue an error immediately. /// Rather, converting or formatting the `DelayedFormat` fails. /// You are recommended to immediately use `DelayedFormat` for this reason. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. /// /// ``` /// # use chrono::NaiveDate; /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Formats the date with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, locale: Locale, ) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new_with_locale(Some(*self), None, items, locale) } /// Formats the date with the specified format string and locale. /// /// See the [`crate::format::strftime`] module on the supported escape /// sequences. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, locale: Locale, ) -> DelayedFormat> { self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) } /// Returns an iterator that steps by days across all representable dates. /// /// # Example /// /// ``` /// # use chrono::NaiveDate; /// /// let expected = [ /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), /// NaiveDate::from_ymd_opt(2016, 2, 28).unwrap(), /// NaiveDate::from_ymd_opt(2016, 2, 29).unwrap(), /// NaiveDate::from_ymd_opt(2016, 3, 1).unwrap(), /// ]; /// /// let mut count = 0; /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).enumerate() { /// assert_eq!(d, expected[idx]); /// count += 1; /// } /// assert_eq!(count, 4); /// /// 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 const fn iter_days(&self) -> NaiveDateDaysIterator { NaiveDateDaysIterator { value: *self } } /// Returns an iterator that steps by weeks across all representable dates. /// /// # Example /// /// ``` /// # use chrono::NaiveDate; /// /// let expected = [ /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), /// NaiveDate::from_ymd_opt(2016, 3, 5).unwrap(), /// NaiveDate::from_ymd_opt(2016, 3, 12).unwrap(), /// NaiveDate::from_ymd_opt(2016, 3, 19).unwrap(), /// ]; /// /// let mut count = 0; /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).enumerate() { /// assert_eq!(d, expected[idx]); /// count += 1; /// } /// assert_eq!(count, 4); /// /// 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 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 } // 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() } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; /// The maximum possible `NaiveDate` (December 31, 262143 CE). pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { /// Returns the year number in the [calendar date](#calendar-date). /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE /// ``` #[inline] fn year(&self) -> i32 { self.year() } /// Returns the month number starting from 1. /// /// The return value ranges from 1 to 12. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); /// ``` #[inline] fn month(&self) -> u32 { self.month() } /// Returns the month number starting from 0. /// /// The return value ranges from 0 to 11. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); /// ``` #[inline] fn month0(&self) -> u32 { self.month() - 1 } /// Returns the day of month starting from 1. /// /// The return value ranges from 1 to 31. (The last day of month differs by months.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// 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), /// 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_opt(y, m, 1).unwrap(); /// /// // ...is preceded by the last day of the original month /// d.pred_opt().unwrap().day() /// } /// /// assert_eq!(ndays_in_month(2015, 8), 31); /// assert_eq!(ndays_in_month(2015, 9), 30); /// assert_eq!(ndays_in_month(2015, 12), 31); /// assert_eq!(ndays_in_month(2016, 2), 29); /// assert_eq!(ndays_in_month(2017, 2), 28); /// ``` #[inline] fn day(&self) -> u32 { self.day() } /// Returns the day of month starting from 0. /// /// The return value ranges from 0 to 30. (The last day of month differs by months.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); /// ``` #[inline] fn day0(&self) -> u32 { self.mdf().day() - 1 } /// Returns the day of year starting from 1. /// /// The return value ranges from 1 to 366. (The last day of year differs by years.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// 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), /// 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_opt(year + 1, 1, 1).unwrap(); /// /// // ...is preceded by the last day of the original year /// d.pred_opt().unwrap().ordinal() /// } /// /// assert_eq!(ndays_in_year(2015), 365); /// assert_eq!(ndays_in_year(2016), 366); /// assert_eq!(ndays_in_year(2017), 365); /// assert_eq!(ndays_in_year(2000), 366); /// assert_eq!(ndays_in_year(2100), 365); /// ``` #[inline] fn ordinal(&self) -> u32 { self.of().ordinal() } /// Returns the day of year starting from 0. /// /// The return value ranges from 0 to 365. (The last day of year differs by years.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_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 } /// Returns the day of week. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); /// ``` #[inline] fn weekday(&self) -> Weekday { self.weekday() } #[inline] fn iso_week(&self) -> IsoWeek { isoweek::iso_week_from_yof(self.year(), self.of()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. /// /// # 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_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_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 { // we need to operate with `mdf` since we should keep the month and day number as is let mdf = self.mdf(); // adjust the flags as needed let flags = YearFlags::from_year(year); let mdf = mdf.with_flags(flags); NaiveDate::from_mdf(year, mdf) } /// Makes a new `NaiveDate` with the month number (starting from 1) changed. /// /// # Errors /// /// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// 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 { self.with_mdf(self.mdf().with_month(month)?) } /// Makes a new `NaiveDate` with the month number (starting from 0) changed. /// /// # Errors /// /// Returns `None` if the resulting date does not exist, or if the value for `month0` is /// invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// 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 { let month = month0.checked_add(1)?; self.with_mdf(self.mdf().with_month(month)?) } /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. /// /// # Errors /// /// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), /// None); // no September 31 /// ``` #[inline] fn with_day(&self, day: u32) -> Option { self.with_mdf(self.mdf().with_day(day)?) } /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. /// /// # Errors /// /// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), /// None); // no September 31 /// ``` #[inline] fn with_day0(&self, day0: u32) -> Option { let day = day0.checked_add(1)?; self.with_mdf(self.mdf().with_day(day)?) } /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. /// /// # Errors /// /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is /// invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(60), /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(366), /// None); // 2015 had only 365 days /// /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(60), /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(366), /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { self.of().with_ordinal(ordinal).map(|of| self.with_of(of)) } /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. /// /// # 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_opt(2015, 1, 1).unwrap().with_ordinal0(59), /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(365), /// None); // 2015 had only 365 days /// /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(59), /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(365), /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { let ordinal = ordinal0.checked_add(1)?; self.with_ordinal(ordinal) } } /// An addition of `Duration` to `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// /// Panics on underflow or overflow. Use [`NaiveDate::checked_add_signed`] to detect that. /// /// # Example /// /// ``` /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// 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)); /// ``` /// /// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed impl Add for NaiveDate { type Output = NaiveDate; #[inline] fn add(self, rhs: OldDuration) -> NaiveDate { self.checked_add_signed(rhs).expect("`NaiveDate + Duration` overflowed") } } impl AddAssign for NaiveDate { #[inline] fn add_assign(&mut self, rhs: OldDuration) { *self = self.add(rhs); } } impl Add for NaiveDate { type Output = NaiveDate; /// An addition of months to `NaiveDate` clamped to valid days in resulting month. /// /// # Panics /// /// Panics if the resulting date would be out of range. /// /// # 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)); /// ``` fn add(self, months: Months) -> Self::Output { self.checked_add_months(months).unwrap() } } impl Sub for NaiveDate { type Output = NaiveDate; /// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. /// /// # Panics /// /// Panics if the resulting date would be out of range. /// /// # 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(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)); /// ``` fn sub(self, months: Months) -> Self::Output { self.checked_sub_months(months).unwrap() } } impl Add for NaiveDate { type Output = NaiveDate; fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).unwrap() } } impl Sub for NaiveDate { type Output = NaiveDate; fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).unwrap() } } /// A subtraction of `Duration` from `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. /// /// Panics on underflow or overflow. Use [`NaiveDate::checked_sub_signed`] to detect that. /// /// # Example /// /// ``` /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// 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)); /// ``` /// /// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed impl Sub for NaiveDate { type Output = NaiveDate; #[inline] fn sub(self, rhs: OldDuration) -> NaiveDate { self.checked_sub_signed(rhs).expect("`NaiveDate - Duration` overflowed") } } impl SubAssign for NaiveDate { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { *self = self.sub(rhs); } } /// Subtracts another `NaiveDate` from the current date. /// Returns a `Duration` of integral numbers. /// /// This does not overflow or underflow at all, /// as all possible output fits in the range of `Duration`. /// /// The implementation is a wrapper around /// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). /// /// # Example /// /// ``` /// use chrono::{Duration, 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), Duration::zero()); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), Duration::days(1)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), Duration::days(-1)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), Duration::days(100)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), Duration::days(365)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), Duration::days(365*4 + 1)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), Duration::days(365*400 + 97)); /// ``` impl Sub for NaiveDate { type Output = OldDuration; #[inline] fn sub(self, rhs: NaiveDate) -> OldDuration { self.signed_duration_since(rhs) } } impl From for NaiveDate { fn from(naive_datetime: NaiveDateTime) -> Self { naive_datetime.date() } } /// Iterator over `NaiveDate` with a step size of one day. #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateDaysIterator { value: NaiveDate, } impl Iterator for NaiveDateDaysIterator { type Item = NaiveDate; fn next(&mut self) -> Option { // We return the current value, and have no way to return `NaiveDate::MAX`. let current = self.value; // This can't panic because current is < NaiveDate::MAX: self.value = current.succ_opt()?; Some(current) } fn size_hint(&self) -> (usize, Option) { 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 { // 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, } impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { let current = self.value; self.value = current.checked_add_signed(OldDuration::weeks(1))?; Some(current) } fn size_hint(&self) -> (usize, Option) { 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 {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { let current = self.value; self.value = current.checked_sub_signed(OldDuration::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). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::NaiveDate; /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Debug for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use core::fmt::Write; let year = self.year(); let mdf = self.mdf(); if (0..=9999).contains(&year) { write_hundreds(f, (year / 100) as u8)?; write_hundreds(f, (year % 100) as u8)?; } else { // ISO 8601 requires the explicit sign for out-of-range years write!(f, "{:+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). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::NaiveDate; /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Display for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } } /// Parsing a `str` into a `NaiveDate` uses the same format, /// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`. /// /// # Example /// /// ``` /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap(); /// assert_eq!("2015-09-18".parse::(), Ok(d)); /// /// let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap(); /// assert_eq!("+12345-6-7".parse::(), Ok(d)); /// /// assert!("foo".parse::().is_err()); /// ``` impl str::FromStr for NaiveDate { type Err = ParseError; fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), Item::Space(""), ]; let mut parsed = Parsed::new(); parse(&mut parsed, s, ITEMS.iter())?; parsed.to_naive_date() } } /// The default value for a NaiveDate is 1st of January 1970. /// /// # Example /// /// ```rust /// use chrono::NaiveDate; /// /// let default_date = NaiveDate::default(); /// assert_eq!(default_date, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); /// ``` impl Default for NaiveDate { fn default() -> Self { NaiveDate::from_ymd_opt(1970, 1, 1).unwrap() } } const fn 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(to_string: F) where F: Fn(&NaiveDate) -> Result, E: ::std::fmt::Debug, { 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#""-262144-01-01""#.into())); assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262143-12-31""#.into())); } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_decodable_json(from_str: F) where F: Fn(&str) -> Result, E: ::std::fmt::Debug, { use std::{i32, i64}; assert_eq!( from_str(r#""2016-07-08""#).ok(), Some(NaiveDate::from_ymd_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#""-262144-01-01""#).ok(), Some(NaiveDate::MIN)); assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(NaiveDate::MAX)); // bad formats assert!(from_str(r#""""#).is_err()); assert!(from_str(r#""20001231""#).is_err()); assert!(from_str(r#""2000-00-00""#).is_err()); assert!(from_str(r#""2000-02-30""#).is_err()); assert!(from_str(r#""2001-02-29""#).is_err()); assert!(from_str(r#""2002-002-28""#).is_err()); assert!(from_str(r#""yyyy-mm-dd""#).is_err()); assert!(from_str(r#"0"#).is_err()); assert!(from_str(r#"20.01"#).is_err()); assert!(from_str(&i32::MIN.to_string()).is_err()); assert!(from_str(&i32::MAX.to_string()).is_err()); assert!(from_str(&i64::MIN.to_string()).is_err()); assert!(from_str(&i64::MAX.to_string()).is_err()); assert!(from_str(r#"{}"#).is_err()); // pre-0.3.0 rustc-serialize format is now invalid assert!(from_str(r#"{"ymdf":20}"#).is_err()); assert!(from_str(r#"null"#).is_err()); } #[cfg(feature = "rustc-serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] mod rustc_serialize { use super::NaiveDate; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; impl Encodable for NaiveDate { fn encode(&self, s: &mut S) -> Result<(), S::Error> { format!("{:?}", self).encode(s) } } impl Decodable for NaiveDate { fn decode(d: &mut D) -> Result { d.read_str()?.parse().map_err(|_| d.error("invalid date")) } } #[cfg(test)] mod tests { use crate::naive::date::{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); } } } #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde { use super::NaiveDate; use core::fmt; use serde::{de, ser}; // TODO not very optimized for space (binary formats would want something better) impl ser::Serialize for NaiveDate { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { struct FormatWrapped<'a, D: 'a> { inner: &'a D, } impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } } serializer.collect_str(&FormatWrapped { inner: &self }) } } struct NaiveDateVisitor; impl<'de> de::Visitor<'de> for NaiveDateVisitor { type Value = NaiveDate; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a formatted date string") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(E::custom) } } impl<'de> de::Deserialize<'de> for NaiveDate { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(NaiveDateVisitor) } } #[cfg(test)] mod tests { use crate::naive::date::{test_decodable_json, test_encodable_json}; use crate::NaiveDate; #[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::duration::Duration; use crate::{Datelike, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, // we use a separate run-time test. #[test] fn test_date_bounds() { let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap(); let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); assert!( NaiveDate::MIN == calculated_min, "`NaiveDate::MIN` should have a year flag {:?}", calculated_min.of().flags() ); assert!( NaiveDate::MAX == calculated_max, "`NaiveDate::MAX` should have a year flag {:?}", calculated_max.of().flags() ); // let's also check that the entire range do not exceed 2^44 seconds // (sometimes used for bounding `Duration` against overflow) let maxsecs = 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 ); } #[test] fn diff_months() { // identity assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) ); // add with months exceeding `i32::MAX` assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3) .unwrap() .checked_add_months(Months::new(i32::MAX as u32 + 1)), None ); // sub with months exceeding `i32::MIN` assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3) .unwrap() .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), None ); // add overflowing year assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); // add underflowing year assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); // sub crossing year 0 boundary assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) ); // add crossing year boundary assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) ); // sub crossing year boundary assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) ); // add clamping day, non-leap year assert_eq!( NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) ); // add to leap day assert_eq!( NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) ); // add into december assert_eq!( NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) ); // sub into december assert_eq!( NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) ); // add into january assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) ); // sub into january assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) ); } #[test] fn test_readme_doomsday() { for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { // even months let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); // nine to five, seven-eleven let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); // "March 0" let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); let weekday = d30.weekday(); let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; assert!(other_dates.iter().all(|d| d.weekday() == weekday)); } } #[test] fn test_date_from_ymd() { let ymd_opt = NaiveDate::from_ymd_opt; assert!(ymd_opt(2012, 0, 1).is_none()); assert!(ymd_opt(2012, 1, 1).is_some()); assert!(ymd_opt(2012, 2, 29).is_some()); assert!(ymd_opt(2014, 2, 29).is_none()); assert!(ymd_opt(2014, 3, 0).is_none()); assert!(ymd_opt(2014, 3, 1).is_some()); assert!(ymd_opt(2014, 3, 31).is_some()); assert!(ymd_opt(2014, 3, 32).is_none()); assert!(ymd_opt(2014, 12, 31).is_some()); assert!(ymd_opt(2014, 13, 1).is_none()); } #[test] fn test_date_from_yo() { let yo_opt = NaiveDate::from_yo_opt; let ymd = |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))); assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); assert_eq!(yo_opt(2012, 367), None); assert_eq!(yo_opt(2014, 0), None); assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); assert_eq!(yo_opt(2014, 366), None); } #[test] fn test_date_from_isoywd() { let isoywd_opt = NaiveDate::from_isoywd_opt; let ymd = |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))); assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); } #[test] fn test_date_from_isoywd_and_iso_week() { for year in 2000..2401 { for week in 1..54 { for &weekday in [ Weekday::Mon, Weekday::Tue, Weekday::Wed, Weekday::Thu, Weekday::Fri, Weekday::Sat, Weekday::Sun, ] .iter() { let d = NaiveDate::from_isoywd_opt(year, week, weekday); if let Some(d) = d { assert_eq!(d.weekday(), weekday); let w = d.iso_week(); assert_eq!(w.year(), year); assert_eq!(w.week(), week); } } } } for year in 2000..2401 { for month in 1..13 { for day in 1..32 { let d = NaiveDate::from_ymd_opt(year, month, day); if let Some(d) = d { let w = d.iso_week(); let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); assert_eq!(d, d_.unwrap()); } } } } } #[test] fn test_date_from_num_days_from_ce() { let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); assert_eq!( from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap()) ); assert_eq!( from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap()) ); assert_eq!( from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap()) ); assert_eq!( from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap()) ); assert_eq!( from_ndays_from_ce(146097 * 5 + 1), Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) ); assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE for days in (-9999..10001).map(|x| x * 100) { assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); } assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); assert_eq!(from_ndays_from_ce(i32::MIN), None); assert_eq!(from_ndays_from_ce(i32::MAX), None); } #[test] fn test_date_from_weekday_of_month_opt() { let ymwd = NaiveDate::from_weekday_of_month_opt; assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); assert_eq!( ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap()) ); assert_eq!( ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap()) ); assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); } #[test] fn test_date_fields() { fn check(year: i32, month: u32, day: u32, ordinal: u32) { let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); assert_eq!(d1.year(), year); assert_eq!(d1.month(), month); assert_eq!(d1.day(), day); assert_eq!(d1.ordinal(), ordinal); let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); assert_eq!(d2.year(), year); assert_eq!(d2.month(), month); assert_eq!(d2.day(), day); assert_eq!(d2.ordinal(), ordinal); assert_eq!(d1, d2); } check(2012, 1, 1, 1); check(2012, 1, 2, 2); check(2012, 2, 1, 32); check(2012, 2, 29, 60); check(2012, 3, 1, 61); check(2012, 4, 9, 100); check(2012, 7, 18, 200); check(2012, 10, 26, 300); check(2012, 12, 31, 366); check(2014, 1, 1, 1); check(2014, 1, 2, 2); check(2014, 2, 1, 32); check(2014, 2, 28, 59); check(2014, 3, 1, 60); check(2014, 4, 10, 100); check(2014, 7, 19, 200); check(2014, 10, 27, 300); check(2014, 12, 31, 365); } #[test] fn test_date_weekday() { assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); // May 20, 1875 = ISO 8601 reference date assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); } #[test] fn test_date_with_fields() { let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); assert_eq!(d.with_year(-100), None); assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); assert_eq!(d.with_year(1900), None); assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_year(2001), None); assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); assert_eq!(d.with_year(i32::MAX), None); let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); assert_eq!(d.with_month(0), None); assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); assert_eq!(d.with_month(2), None); assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); assert_eq!(d.with_month(13), None); assert_eq!(d.with_month(u32::MAX), None); let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); assert_eq!(d.with_day(0), None); assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_day(30), None); assert_eq!(d.with_day(u32::MAX), None); let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); assert_eq!(d.with_ordinal(0), None); assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); assert_eq!(d.with_ordinal(367), None); assert_eq!(d.with_ordinal(u32::MAX), None); } #[test] fn test_date_num_days_from_ce() { assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); for year in -9999..10001 { assert_eq!( NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 ); } } #[test] fn test_date_succ() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); } #[test] fn test_date_pred() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); } #[test] fn test_date_add() { fn check((y1, m1, d1): (i32, u32, u32), rhs: Duration, 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))); // always round towards zero check((2014, 1, 1), Duration::seconds(-86399), Some((2014, 1, 1))); check((2014, 1, 1), Duration::days(1), Some((2014, 1, 2))); check((2014, 1, 1), Duration::days(-1), Some((2013, 12, 31))); check((2014, 1, 1), Duration::days(364), Some((2014, 12, 31))); check((2014, 1, 1), Duration::days(365 * 4 + 1), Some((2018, 1, 1))); check((2014, 1, 1), Duration::days(365 * 400 + 97), Some((2414, 1, 1))); check((-7, 1, 1), Duration::days(365 * 12 + 3), Some((5, 1, 1))); // overflow check check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); check((0, 1, 1), Duration::max_value(), None); check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); check((0, 1, 1), Duration::min_value(), None); } #[test] fn test_date_sub() { fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Duration) { let lhs = NaiveDate::from_ymd_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((MAX_YEAR, 12, 31), (0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64)); check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64)); } #[test] fn test_date_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), 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 = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 1); date += Duration::days(10); assert_eq!(date, ymd(2016, 10, 11)); date += Duration::days(30); assert_eq!(date, ymd(2016, 11, 10)); } #[test] fn test_date_subassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 11); date -= Duration::days(10); assert_eq!(date, ymd(2016, 10, 1)); date -= Duration::days(2); assert_eq!(date, ymd(2016, 9, 29)); } #[test] fn test_date_fmt() { assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); // the format specifier should have no effect on `NaiveTime` assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); assert_eq!( format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07" ); } #[test] fn test_date_from_str() { // valid cases let valid = [ "-0000000123456-1-2", " -123456 - 1 - 2 ", "-12345-1-2", "-1234-12-31", "-7-6-5", "350-2-28", "360-02-29", "0360-02-29", "2015-2 -18", "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` 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::() { Ok(d) => d, Err(e) => { 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 \ `{:?}` 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 "Fri, 09 Aug 2013 GMT", // valid date, wrong format "Sat Jun 30 2012", // valid date, wrong format "1441497364.649", // valid datetime, wrong format "+1441497364.649", // valid datetime, wrong format "+1441497364", // valid datetime, wrong format "2014/02/03", // valid date, wrong format "2014", // datetime missing data "2014-01", // datetime missing data "2014-01-00", // invalid day "2014-11-32", // invalid day "2014-13-01", // invalid month "2014-13-57", // invalid month, day "9999999-9-9", // invalid year (out of bounds) ]; for &s in &invalid { eprintln!("test_date_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } } #[test] fn test_date_parse_from_str() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!( NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), Ok(ymd(2015, 2, 2)) ); assert_eq!( NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9)) ); assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient assert_eq!( NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), NaiveDate::from_ymd_opt(2020, 1, 12), ); assert_eq!( NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), NaiveDate::from_ymd_opt(2019, 1, 13), ); } #[test] fn test_day_iterator_limit() { assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); assert_eq!( NaiveDate::from_ymd_opt(-262144, 1, 3).unwrap().iter_days().rev().take(4).count(), 2 ); } #[test] fn test_week_iterator_limit() { assert_eq!( NaiveDate::from_ymd_opt(262143, 12, 12).unwrap().iter_weeks().take(4).count(), 2 ); assert_eq!( NaiveDate::from_ymd_opt(-262144, 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()); } } // 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 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. const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4 - (MIN_YEAR + 400_000) / 100 + (MIN_YEAR + 400_000) / 400 - 146_097_000; // only used for testing, but duplicated in naive::datetime const MAX_BITS: usize = 44; } chrono-0.4.31/src/naive/datetime/mod.rs000064400000000000000000002356300072674642500160540ustar 00000000000000// 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"))] use core::borrow::Borrow; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] 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::{expect, DateTime, Datelike, LocalResult, Months, 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 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`. #[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`](./struct.NaiveDate.html). /// /// ``` /// 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](../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_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(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", 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`](../offset/trait.TimeZone.html#method.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 fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); datetime.expect("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 fn from_timestamp_millis(millis: i64) -> Option { 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 fn from_timestamp_micros(micros: i64) -> Option { let secs = micros.div_euclid(1_000_000); let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; 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 fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { let days = secs.div_euclid(86_400); let secs = secs.rem_euclid(86_400); let date = i32::try_from(days) .ok() .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, 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_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap())); /// assert_eq!(parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_micro_opt(13, 23, 45, 678_900).unwrap())); /// ``` /// /// Offset is ignored for the purpose of parsing. /// /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// ``` /// /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by /// treating any time of the form `hh:mm:60` as a leap second. /// (This equally applies to the formatting, so the round trip is possible.) /// /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_milli_opt(8, 59, 59, 1_123).unwrap())); /// ``` /// /// Missing seconds are assumed to be zero, /// but out-of-bound times or insufficient fields are errors otherwise. /// /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap())); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); /// assert!(parse_from_str("94/9/4 17:60", "%y/%m/%d %H:%M").is_err()); /// assert!(parse_from_str("94/9/4 24:00:00", "%y/%m/%d %H:%M:%S").is_err()); /// ``` /// /// All parsed fields should be consistent to each other, otherwise it's an error. /// /// ``` /// # use chrono::NaiveDateTime; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// let fmt = "%Y-%m-%d %H:%M:%S = UNIX timestamp %s"; /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); /// ``` /// /// Years before 1 BCE or after 9999 CE, require an initial sign /// ///``` /// # use chrono::NaiveDateTime; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// let fmt = "%Y-%m-%d %H:%M:%S"; /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); ///``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_naive_datetime_with_offset(0) // no offset adjustment } /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a /// slice with the remaining portion of the string. /// See the [`format::strftime` module](../format/strftime/index.html) /// 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 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. /// /// # 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 fn timestamp_millis(&self) -> i64 { let as_ms = self.timestamp() * 1000; as_ms + i64::from(self.timestamp_subsec_millis()) } /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. /// /// Note that this does *not* account for the timezone! /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. /// /// # 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 fn timestamp_micros(&self) -> i64 { let as_us = self.timestamp() * 1_000_000; as_us + i64::from(self.timestamp_subsec_micros()) } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. /// /// Note that this does *not* account for the timezone! /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. /// /// # Panics /// /// 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:44.0 and /// 2262-04-11T23:47:16.854775804. #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { self.timestamp_nanos_opt() .expect("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:44.0 and /// 2262-04-11T23:47:16.854775804. /// /// # 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 fn timestamp_nanos_opt(&self) -> Option { let mut timestamp = self.timestamp(); let mut timestamp_subsec_nanos = i64::from(self.timestamp_subsec_nanos()); // 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 . if timestamp < 0 && timestamp_subsec_nanos > 0 { timestamp_subsec_nanos -= 1_000_000_000; timestamp += 1; } timestamp.checked_mul(1_000_000_000).and_then(|ns| ns.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 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 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 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**. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// use chrono::{Duration, 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(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_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(Duration::milliseconds(450)), /// Some(hmsm(3, 5, 8, 430))); /// ``` /// /// Overflow returns `None`. /// /// ``` /// # use chrono::{Duration, 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(Duration::days(1_000_000_000)), None); /// ``` /// /// Leap seconds are handled, /// but the addition assumes that it is the only leap second happened. /// /// ``` /// # use chrono::{Duration, 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(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_opt(3, 5, 59, 300).unwrap())); /// ``` #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); // early checking to avoid overflow in OldDuration::seconds if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { return None; } let date = self.date.checked_add_signed(OldDuration::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 fn checked_add_months(self, rhs: Months) -> Option { Some(Self { date: self.date.checked_add_months(rhs)?, time: self.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**. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// /// # Example /// /// ``` /// use chrono::{Duration, 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(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_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(Duration::milliseconds(670)), /// Some(hmsm(3, 5, 6, 780))); /// ``` /// /// Overflow returns `None`. /// /// ``` /// # use chrono::{Duration, 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(Duration::days(1_000_000_000)), None); /// ``` /// /// Leap seconds are handled, /// but the subtraction assumes that it is the only leap second happened. /// /// ``` /// # use chrono::{Duration, 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(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_opt(3, 6, 0, 300).unwrap())); /// ``` #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); // early checking to avoid overflow in OldDuration::seconds if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { return None; } let date = self.date.checked_sub_signed(OldDuration::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 fn checked_sub_months(self, rhs: Months) -> Option { Some(Self { date: 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 fn checked_add_days(self, days: Days) -> Option { Some(Self { date: 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 fn checked_sub_days(self, days: Days) -> Option { Some(Self { date: 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::{Duration, 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()), /// 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_opt(0, 7, 6, 500).unwrap().signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), /// 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. /// /// ``` /// # use chrono::{Duration, 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()), /// Duration::seconds(3600) + Duration::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` #[must_use] 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_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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new(Some(self.date), Some(self.time), items) } /// Formats the combined date and time with the specified format string. /// See the [`format::strftime` module](../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_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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Converts the `NaiveDateTime` into the timezone-aware `DateTime` /// 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(&self, tz: Tz) -> LocalResult> { tz.from_local_datetime(self) } /// Converts the `NaiveDateTime` into the timezone-aware `DateTime`. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Utc}; /// let dt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); /// assert_eq!(dt.timezone(), Utc); /// ``` #[must_use] pub fn and_utc(&self) -> DateTime { Utc.from_utc_datetime(self) } /// 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 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`](./struct.NaiveDate.html#method.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`](./struct.NaiveDate.html#method.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`](./struct.NaiveDate.html#method.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 { 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 { 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 { 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 { 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 { 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 { 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 { 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::nanosecond`] method. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.nanosecond(), 789_000_000); /// ``` #[inline] fn nanosecond(&self) -> u32 { self.time.nanosecond() } /// Makes a new `NaiveDateTime` with the hour number changed. /// /// See also the [`NaiveTime::with_hour`] method. /// /// # Errors /// /// Returns `None` if the value for `hour` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_hour(7), /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap())); /// assert_eq!(dt.with_hour(24), None); /// ``` #[inline] fn with_hour(&self, hour: u32) -> Option { self.time.with_hour(hour).map(|t| NaiveDateTime { time: t, ..*self }) } /// Makes a new `NaiveDateTime` with the minute number changed. /// /// See also the [`NaiveTime::with_minute`] method. /// /// # Errors /// /// Returns `None` if the value for `minute` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_minute(45), /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 45, 56, 789).unwrap())); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] fn with_minute(&self, min: u32) -> Option { self.time.with_minute(min).map(|t| NaiveDateTime { time: t, ..*self }) } /// Makes a new `NaiveDateTime` with the second number changed. /// /// As with the [`second`](#method.second) method, /// the input range is restricted to 0 through 59. /// /// See also the [`NaiveTime::with_second`] method. /// /// # Errors /// /// Returns `None` if the value for `second` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_second(17), /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 17, 789).unwrap())); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] fn with_second(&self, sec: u32) -> Option { self.time.with_second(sec).map(|t| NaiveDateTime { time: t, ..*self }) } /// Makes a new `NaiveDateTime` with nanoseconds since the whole non-leap second changed. /// /// Returns `None` when the resulting `NaiveDateTime` would be invalid. /// As with the [`NaiveDateTime::nanosecond`] method, /// the input range can exceed 1,000,000,000 for leap seconds. /// /// See also the [`NaiveTime::with_nanosecond`] method. /// /// # Errors /// /// Returns `None` if `nanosecond >= 2,000,000,000`. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 1_333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` #[inline] fn with_nanosecond(&self, nano: u32) -> Option { self.time.with_nanosecond(nano).map(|t| NaiveDateTime { time: t, ..*self }) } } /// An addition of `Duration` to `NaiveDateTime` yields another `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. Use [`NaiveDateTime::checked_add_signed`] /// to detect that. /// /// # Example /// /// ``` /// use chrono::{Duration, 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) + 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_opt(3, 5, 7).unwrap()); /// assert_eq!(hms(3, 5, 7) + Duration::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) + Duration::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::{Duration, 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 + 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_opt(3, 5, 59, 300).unwrap()); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn add(self, rhs: OldDuration) -> NaiveDateTime { self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") } } impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn add(self, rhs: Duration) -> NaiveDateTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") } } impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { *self = self.add(rhs); } } impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { *self = self.add(rhs); } } impl Add for NaiveDateTime { type Output = NaiveDateTime; /// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics /// /// Panics 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() + 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() /// ); /// ``` fn add(self, rhs: Months) -> Self::Output { Self { date: self.date.checked_add_months(rhs).unwrap(), time: self.time } } } /// 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] 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 on underflow or overflow. Use [`NaiveDateTime::checked_sub_signed`] /// to detect that. /// /// # Example /// /// ``` /// use chrono::{Duration, 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) - 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_opt(3, 5, 7).unwrap()); /// assert_eq!(hms(3, 5, 7) - Duration::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) - Duration::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::{Duration, 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 - 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_opt(3, 6, 0, 300).unwrap()); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn sub(self, rhs: OldDuration) -> NaiveDateTime { self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") } } impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn sub(self, rhs: Duration) -> NaiveDateTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") } } impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { *self = self.sub(rhs); } } impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { *self = self.sub(rhs); } } /// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics /// /// Panics if the resulting date would be out of range. /// /// # Example /// /// ``` /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() - Months::new(11), /// NaiveDate::from_ymd_opt(2013, 02, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() /// ); /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() - Months::new(12), /// NaiveDate::from_ymd_opt(2013, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() /// ); /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() - Months::new(13), /// NaiveDate::from_ymd_opt(2012, 12, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() /// ); /// ``` impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, rhs: Months) -> Self::Output { Self { date: self.date.checked_sub_months(rhs).unwrap(), time: self.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**. /// /// The implementation is a wrapper around [`NaiveDateTime::signed_duration_since`]. /// /// # Example /// /// ``` /// use chrono::{Duration, 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(), 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_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that no other leap /// seconds happened. /// /// ``` /// # use chrono::{Duration, 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(), /// Duration::seconds(3600) + Duration::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` impl Sub for NaiveDateTime { type Output = OldDuration; #[inline] fn sub(self, rhs: NaiveDateTime) -> OldDuration { self.signed_duration_since(rhs) } } impl Add for NaiveDateTime { type Output = NaiveDateTime; fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).unwrap() } } impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).unwrap() } } /// 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::(), Ok(dt)); /// /// let dt = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap().and_hms_milli_opt(7, 59, 59, 1_500).unwrap(); // leap second /// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); /// /// assert!("foo".parse::().is_err()); /// ``` impl str::FromStr for NaiveDateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive? Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), Item::Space(""), ]; let mut parsed = Parsed::new(); parse(&mut parsed, s, ITEMS.iter())?; parsed.to_naive_datetime_with_offset(0) } } /// The default value for a NaiveDateTime is 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(to_string: F) where F: Fn(&NaiveDateTime) -> Result, 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#""-262144-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#""+262143-12-31T23:59:60.999999999""#.into()) ); } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_decodable_json(from_str: F) where F: Fn(&str) -> Result, E: ::std::fmt::Debug, { assert_eq!( from_str(r#""2016-07-08T09:10:48.090""#).ok(), Some( NaiveDate::from_ymd_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#""-262144-01-01T00:00:00""#).ok(), Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) ); assert_eq!( from_str(r#""+262143-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#""+262143-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(from_str: F) where F: Fn(&str) -> Result, 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" ); } chrono-0.4.31/src/naive/datetime/rustc_serialize.rs000064400000000000000000000036240072674642500205000ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] use super::NaiveDateTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use std::ops::Deref; impl Encodable for NaiveDateTime { fn encode(&self, s: &mut S) -> Result<(), S::Error> { format!("{:?}", self).encode(s) } } impl Decodable for NaiveDateTime { fn decode(d: &mut D) -> Result { d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) } } /// A `DateTime` that can be deserialized from a seconds-based timestamp #[derive(Debug)] #[deprecated( since = "1.4.2", note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" )] pub struct TsSeconds(NaiveDateTime); #[allow(deprecated)] impl From for NaiveDateTime { /// Pull the internal NaiveDateTime out #[allow(deprecated)] fn from(obj: TsSeconds) -> NaiveDateTime { obj.0 } } #[allow(deprecated)] impl Deref for TsSeconds { type Target = NaiveDateTime; #[allow(deprecated)] fn deref(&self) -> &Self::Target { &self.0 } } #[allow(deprecated)] impl Decodable for TsSeconds { #[allow(deprecated)] fn decode(d: &mut D) -> Result { Ok(TsSeconds( NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) .ok_or_else(|| d.error("invalid timestamp"))?, )) } } #[cfg(test)] 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); } } chrono-0.4.31/src/naive/datetime/serde.rs000064400000000000000000001127700072674642500163760ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] 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(&self, serializer: S) -> Result where S: ser::Serializer, { struct FormatWrapped<'a, D: 'a> { inner: &'a D, } impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } } serializer.collect_str(&FormatWrapped { inner: &self }) } } struct NaiveDateTimeVisitor; impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a formatted date and time string") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(E::custom) } } impl<'de> de::Deserialize<'de> for NaiveDateTime { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(NaiveDateTimeVisitor) } } /// Used to serialize/deserialize from nanosecond-precision timestamps /// /// # Example: /// /// ```rust /// # 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(dt: &NaiveDateTime, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( "value out of range for a timestamp with nanosecond precision", ))?) } /// Deserialize a `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 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(self, value: i64) -> Result 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(self, value: u64) -> Result 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 /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_nanoseconds_option { use core::fmt; use serde::{de, ser}; use super::ts_nanoseconds::NanoSecondsTimestampVisitor; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of nanoseconds since the epoch or none /// /// Intended for use with `serde`s `serialize_with` attribute. /// /// # Errors /// /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an /// error on an out of range `DateTime`. /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_nano_tsopt")] /// time: Option /// } /// /// let my_s = S { /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.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 /// } /// /// 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, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionNanoSecondsTimestampVisitor) } struct OptionNanoSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in nanoseconds or none") } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in nanoseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Used to serialize/deserialize from microsecond-precision timestamps /// /// # Example: /// /// ```rust /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds")] /// time: NaiveDateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds { use core::fmt; use serde::{de, ser}; use 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(dt: &NaiveDateTime, serializer: S) -> Result 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 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(self, value: i64) -> Result where E: de::Error, { NaiveDateTime::from_timestamp_micros(value) .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result 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 /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds_option { use core::fmt; use serde::{de, ser}; use super::ts_microseconds::MicroSecondsTimestampVisitor; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of microseconds since the epoch or none /// /// Intended for use with `serde`s `serialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_microseconds_option::serialize as to_micro_tsopt; /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_micro_tsopt")] /// time: Option /// } /// /// let my_s = S { /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.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 /// } /// /// 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, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionMicroSecondsTimestampVisitor) } struct OptionMicroSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in microseconds or none") } /// Deserialize a timestamp in microseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in microseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in microseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Used to serialize/deserialize from millisecond-precision timestamps /// /// # Example: /// /// ```rust /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds")] /// time: NaiveDateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds { use core::fmt; use serde::{de, ser}; use 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(dt: &NaiveDateTime, serializer: S) -> Result 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 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(self, value: i64) -> Result where E: de::Error, { NaiveDateTime::from_timestamp_millis(value) .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { NaiveDateTime::from_timestamp_opt( (value / 1000) as i64, ((value % 1000) * 1_000_000) as u32, ) .ok_or_else(|| E::custom(ne_timestamp(value))) } } } /// Ser/de to/from optional timestamps in milliseconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds_option")] /// time: Option /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds_option { use core::fmt; use serde::{de, ser}; use super::ts_milliseconds::MilliSecondsTimestampVisitor; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of milliseconds since the epoch or none /// /// Intended for use with `serde`s `serialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_milliseconds_option::serialize as to_milli_tsopt; /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_milli_tsopt")] /// time: Option /// } /// /// let my_s = S { /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.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 /// } /// /// 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, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionMilliSecondsTimestampVisitor) } struct OptionMilliSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in milliseconds or none") } /// Deserialize a timestamp in milliseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in milliseconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in milliseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } /// Used to serialize/deserialize from second-precision timestamps /// /// # Example: /// /// ```rust /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_seconds; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds")] /// time: NaiveDateTime /// } /// /// let time = NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds { use core::fmt; use serde::{de, ser}; use 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(dt: &NaiveDateTime, serializer: S) -> Result 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 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(self, value: i64) -> Result where E: de::Error, { NaiveDateTime::from_timestamp_opt(value, 0) .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { NaiveDateTime::from_timestamp_opt(value as i64, 0) .ok_or_else(|| E::custom(ne_timestamp(value))) } } } /// Ser/de to/from optional timestamps in seconds /// /// Intended for use with `serde`'s `with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_seconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds_option")] /// time: Option /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); /// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds_option { use core::fmt; use serde::{de, ser}; use super::ts_seconds::SecondsTimestampVisitor; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of seconds since the epoch or none /// /// Intended for use with `serde`s `serialize_with` attribute. /// /// # Example: /// /// ```rust /// # use chrono::naive::{NaiveDate, NaiveDateTime}; /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_seconds_option::serialize as to_tsopt; /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_tsopt")] /// time: Option /// } /// /// let 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(opt: &Option, serializer: S) -> Result where S: ser::Serializer, { match *opt { Some(ref dt) => serializer.serialize_some(&dt.timestamp()), None => serializer.serialize_none(), } } /// Deserialize a `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 /// } /// /// 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, D::Error> where D: de::Deserializer<'de>, { d.deserialize_option(OptionSecondsTimestampVisitor) } struct OptionSecondsTimestampVisitor; impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a unix timestamp in seconds or none") } /// Deserialize a timestamp in seconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_i64(SecondsTimestampVisitor).map(Some) } /// Deserialize a timestamp in seconds since the epoch fn visit_none(self) -> Result where E: de::Error, { Ok(None) } /// Deserialize a timestamp in seconds since the epoch fn visit_unit(self) -> Result where E: de::Error, { Ok(None) } } } // lik? function to convert a LocalResult into a serde-ish Result pub(crate) fn serde_from(me: LocalResult, ts: &V) -> Result where E: de::Error, V: fmt::Display, T: fmt::Display, { match me { LocalResult::None => Err(E::custom(ne_timestamp(ts))), LocalResult::Ambiguous(min, max) => { Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) } LocalResult::Single(val) => Ok(val), } } enum SerdeError { NonExistent { timestamp: V }, Ambiguous { timestamp: V, min: D, max: D }, } /// Construct a [`SerdeError::NonExistent`] fn ne_timestamp(ts: T) -> SerdeError { SerdeError::NonExistent:: { timestamp: ts } } impl fmt::Debug for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ChronoSerdeError({})", self) } } // impl core::error::Error for SerdeError {} impl fmt::Display for SerdeError { 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, #[serde(with = "ts_nanoseconds_option")] two: Option>, } let expected = Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) }; let bytes: Vec = serialize(&expected).unwrap(); let actual = deserialize::(&(bytes)).unwrap(); assert_eq!(expected, actual); } } chrono-0.4.31/src/naive/datetime/tests.rs000064400000000000000000000435630072674642500164410ustar 00000000000000use super::NaiveDateTime; use crate::duration::Duration as OldDuration; use crate::NaiveDate; use crate::{Datelike, FixedOffset, 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(any(feature = "alloc", feature = "std"))] 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(any(feature = "alloc", feature = "std"))] 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() { 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: OldDuration, 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), OldDuration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); check( (2014, 5, 6, 7, 8, 9), OldDuration::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8)), ); check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); check((2014, 5, 6, 7, 8, 9), OldDuration::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 + OldDuration::seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + OldDuration::seconds(86_400), None); check((0, 1, 1, 0, 0, 0), OldDuration::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 - OldDuration::seconds(1), None); check((0, 1, 1, 0, 0, 0), OldDuration::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)), OldDuration::zero() ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), OldDuration::seconds(1) ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), OldDuration::seconds(-1) ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), OldDuration::seconds(86399) ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), OldDuration::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 += OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); date += OldDuration::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 -= OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); date -= OldDuration::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::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; let s_ = format!("{:?}", d); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ `{:?}` does not match", s, d, d_ ); } // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ "", // empty "x", // invalid / missing data "15", // missing data "15:8:9", // looks like a time (invalid date) "15-8-9", // looks like a date (invalid) "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format "Sat Jun 30 23:59:60 2012", // valid date, wrong format "1441497364.649", // valid date, wrong format "+1441497364.649", // valid date, wrong format "+1441497364", // valid date, wrong format "2014/02/03 04:05:06", // valid date, wrong format "2015-15-15T15:15:15", // invalid date "2012-12-12T12:12:12x", // bad timezone / trailing literal "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal "2012-123-12T12:12:12", // invalid month "2012-12-12t12:12:12", // bad divider 't' "2012-12-12 12:12:12", // missing divider 'T' "2012-12-12T12:12:12Z", // trailing char 'Z' "+ 82701-123-12T12:12:12", // strange year, invalid month "+802701-123-12T12:12:12", // out-of-bound year, invalid month ]; for &s in &invalid { eprintln!("test_datetime_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } } #[test] fn test_datetime_parse_from_str() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let ymdhmsn = |y, m, d, h, n, s, nano| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap() }; assert_eq!( NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) ); // ignore offset assert_eq!( NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), Ok(ymdhms(2015, 2, 2, 0, 0, 0)) ); assert_eq!( NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(ymdhms(2013, 8, 9, 23, 54, 35)) ); assert!(NaiveDateTime::parse_from_str( "Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT" ) .is_err()); assert!(NaiveDateTime::parse_from_str("2014-5-7 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 + OldDuration::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 + OldDuration::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 + OldDuration::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); } chrono-0.4.31/src/naive/internals.rs000064400000000000000000001070220072674642500154710ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! The internal implementation of the calendar and ordinal date. //! //! The current implementation is optimized for determining year, month, day and day of week. //! 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar, //! which are included in every packed `NaiveDate` instance. //! The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is //! based on the moderately-sized lookup table (~1.5KB) //! and the packed representation is chosen for the efficient lookup. //! Every internal data structure does not validate its input, //! but the conversion keeps the valid value valid and the invalid value invalid //! so that the user-facing `NaiveDate` can validate the input as late as possible. #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] use crate::Weekday; use core::fmt; /// The internal date representation: `year << 13 | Of` pub(super) type DateImpl = i32; pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13; pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; /// The year flags (aka the dominical letter). /// /// There are 14 possible classes of year in the Gregorian calendar: /// common and leap years starting with Monday through Sunday. /// The `YearFlags` stores this information into 4 bits `abbb`, /// where `a` is `1` for the common year (simplifies the `Of` validation) /// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year /// (simplifies the day of week calculation from the 1-based ordinal). #[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, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, // 100 C, 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, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, // 200 E, 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, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, // 300 G, 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, 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, // 400 ]; const YEAR_DELTAS: &[u8; 401] = &[ 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, // 200 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, // 300 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97, 97, 97, // 400+1 ]; 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 - YEAR_DELTAS[year_mod_400 as usize] as u32; } else { ordinal0 -= delta; } (year_mod_400, ordinal0 + 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] #[must_use] pub const fn from_year(year: i32) -> YearFlags { let year = year.rem_euclid(400); YearFlags::from_year_mod_400(year) } #[inline] pub(super) const fn from_year_mod_400(year: i32) -> YearFlags { YEAR_TO_FLAGS[year as usize] } #[inline] pub(super) const fn ndays(&self) -> u32 { let YearFlags(flags) = *self; 366 - (flags >> 3) as u32 } #[inline] pub(super) const fn isoweek_delta(&self) -> u32 { let YearFlags(flags) = *self; let mut delta = (flags & 0b0111) as u32; if delta < 3 { delta += 7; } delta } #[inline] pub(super) const fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } } impl fmt::Debug for YearFlags { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let YearFlags(flags) = *self; match flags { 0o15 => "A".fmt(f), 0o05 => "AG".fmt(f), 0o14 => "B".fmt(f), 0o04 => "BA".fmt(f), 0o13 => "C".fmt(f), 0o03 => "CB".fmt(f), 0o12 => "D".fmt(f), 0o02 => "DC".fmt(f), 0o11 => "E".fmt(f), 0o01 => "ED".fmt(f), 0o10 => "F?".fmt(f), 0o00 => "FE?".fmt(f), // non-canonical 0o17 => "F".fmt(f), 0o07 => "FE".fmt(f), 0o16 => "G".fmt(f), 0o06 => "GF".fmt(f), _ => write!(f, "YearFlags({})", flags), } } } // 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; 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 XX, XX, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1 XX, XX, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, XX, XX, XX, XX, XX, // 2 XX, XX, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, // 3 XX, XX, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, XX, XX, // 4 XX, XX, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, // 5 XX, XX, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, XX, XX, // 6 XX, XX, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, // 7 XX, XX, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, // 8 XX, XX, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, XX, XX, // 9 XX, XX, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, // 10 XX, XX, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, XX, XX, // 11 XX, XX, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, // 12 ]; 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, // 2 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, // 3 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, // 4 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, // 5 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, // 6 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, // 7 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, // 8 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, // 9 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, // 10 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, // 11 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, // 12 ]; /// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. /// /// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), /// which is an index to the `OL_TO_MDL` lookup table. /// /// The methods implemented on `Of` always return a valid value. #[derive(PartialEq, PartialOrd, Copy, Clone)] pub(super) struct Of(u32); impl Of { #[inline] pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { let of = Of((ordinal << 4) | flags as u32); of.validate() } 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(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option { let mdl = mdf >> 3; 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(super) const fn inner(&self) -> u32 { self.0 } /// Returns `(ordinal << 1) | leap-year-flag`. #[inline] const fn ol(&self) -> u32 { self.0 >> 3 } #[inline] const fn validate(self) -> Option { let ol = self.ol(); match ol >= MIN_OL && ol <= MAX_OL { true => Some(self), false => None, } } #[inline] pub(super) const fn ordinal(&self) -> u32 { self.0 >> 4 } #[inline] pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { 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(super) const fn weekday(&self) -> Weekday { let Of(of) = *self; weekday_from_u32_mod7((of >> 4) + (of & 0b111)) } #[inline] 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_mod7(weekord)) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] 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(super) const fn succ(&self) -> Option { 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(super) const fn pred(&self) -> Option { match self.ordinal() { 1 => None, _ => Some(Of(self.0 - (1 << 4))), } } } impl fmt::Debug for Of { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Of(of) = *self; write!( f, "Of(({} << 4) | {:#04o} /*{:?}*/)", of >> 4, of & 0b1111, YearFlags((of & 0b1111) as u8) ) } } /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` /// /// 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(super) struct Mdf(u32); impl Mdf { #[inline] pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { match month >= 1 && month <= 12 && day >= 1 && day <= 31 { true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), false => None, } } #[inline] pub(super) const fn from_of(Of(of): Of) -> Mdf { let ol = of >> 3; 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) } } #[cfg(test)] pub(super) const fn valid(&self) -> bool { let Mdf(mdf) = *self; let mdl = mdf >> 3; 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(super) const fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } #[inline] pub(super) const fn with_month(&self, month: u32) -> Option { if month > 12 { return None; } let Mdf(mdf) = *self; Some(Mdf((mdf & 0b1_1111_1111) | (month << 9))) } #[inline] pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } #[inline] pub(super) const fn with_day(&self, day: u32) -> Option { if day > 31 { return None; } let Mdf(mdf) = *self; Some(Mdf((mdf & !0b1_1111_0000) | (day << 4))) } #[inline] pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; Mdf((mdf & !0b1111) | flags as u32) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_of(&self) -> Option { Of::from_mdf(*self) } } impl fmt::Debug for Mdf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Mdf(mdf) = *self; write!( f, "Mdf(({} << 9) | ({} << 4) | {:#04o} /*{:?}*/)", mdf >> 9, (mdf >> 4) & 0b1_1111, mdf & 0b1111, YearFlags((mdf & 0b1111) as u8) ) } } /// 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 { 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 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]; const FLAGS: [YearFlags; 14] = [A, B, C, D, E, F, G, AG, BA, CB, DC, ED, FE, GF]; #[test] fn test_year_flags_ndays_from_year() { assert_eq!(YearFlags::from_year(2014).ndays(), 365); assert_eq!(YearFlags::from_year(2012).ndays(), 366); assert_eq!(YearFlags::from_year(2000).ndays(), 366); assert_eq!(YearFlags::from_year(1900).ndays(), 365); assert_eq!(YearFlags::from_year(1600).ndays(), 366); assert_eq!(YearFlags::from_year(1).ndays(), 365); assert_eq!(YearFlags::from_year(0).ndays(), 366); // 1 BCE (proleptic Gregorian) assert_eq!(YearFlags::from_year(-1).ndays(), 365); // 2 BCE assert_eq!(YearFlags::from_year(-4).ndays(), 366); // 5 BCE assert_eq!(YearFlags::from_year(-99).ndays(), 365); // 100 BCE assert_eq!(YearFlags::from_year(-100).ndays(), 365); // 101 BCE assert_eq!(YearFlags::from_year(-399).ndays(), 365); // 400 BCE assert_eq!(YearFlags::from_year(-400).ndays(), 366); // 401 BCE } #[test] fn test_year_flags_nisoweeks() { assert_eq!(A.nisoweeks(), 52); assert_eq!(B.nisoweeks(), 52); assert_eq!(C.nisoweeks(), 52); assert_eq!(D.nisoweeks(), 53); assert_eq!(E.nisoweeks(), 52); assert_eq!(F.nisoweeks(), 52); assert_eq!(G.nisoweeks(), 52); assert_eq!(AG.nisoweeks(), 52); assert_eq!(BA.nisoweeks(), 52); assert_eq!(CB.nisoweeks(), 52); assert_eq!(DC.nisoweeks(), 53); assert_eq!(ED.nisoweeks(), 53); assert_eq!(FE.nisoweeks(), 52); assert_eq!(GF.nisoweeks(), 52); } #[test] fn test_of() { fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { 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.validate().is_some() == expected, "ordinal {} = {:?} should be {} for dominical year {:?}", ordinal, of, if expected { "valid" } else { "invalid" }, flags ); } } for &flags in NONLEAP_FLAGS.iter() { check(false, flags, 0, 0); check(true, flags, 1, 365); check(false, flags, 366, 1024); check(false, flags, u32::MAX, u32::MAX); } for &flags in LEAP_FLAGS.iter() { check(false, flags, 0, 0); check(true, flags, 1, 366); check(false, flags, 367, 1024); check(false, flags, u32::MAX, u32::MAX); } } #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { for month in 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 {:?}", month, day, mdf, if expected { "valid" } else { "invalid" }, flags ); } } } for &flags in NONLEAP_FLAGS.iter() { check(false, flags, 0, 0, 0, 1024); check(false, flags, 0, 0, 16, 0); check(true, flags, 1, 1, 1, 31); check(false, flags, 1, 32, 1, 1024); check(true, flags, 2, 1, 2, 28); check(false, flags, 2, 29, 2, 1024); check(true, flags, 3, 1, 3, 31); check(false, flags, 3, 32, 3, 1024); check(true, flags, 4, 1, 4, 30); check(false, flags, 4, 31, 4, 1024); check(true, flags, 5, 1, 5, 31); check(false, flags, 5, 32, 5, 1024); check(true, flags, 6, 1, 6, 30); check(false, flags, 6, 31, 6, 1024); check(true, flags, 7, 1, 7, 31); check(false, flags, 7, 32, 7, 1024); check(true, flags, 8, 1, 8, 31); check(false, flags, 8, 32, 8, 1024); check(true, flags, 9, 1, 9, 30); check(false, flags, 9, 31, 9, 1024); check(true, flags, 10, 1, 10, 31); check(false, flags, 10, 32, 10, 1024); check(true, flags, 11, 1, 11, 30); check(false, flags, 11, 31, 11, 1024); check(true, flags, 12, 1, 12, 31); check(false, flags, 12, 32, 12, 1024); check(false, flags, 13, 0, 16, 1024); check(false, flags, u32::MAX, 0, u32::MAX, 1024); check(false, flags, 0, u32::MAX, 16, u32::MAX); check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX); } for &flags in LEAP_FLAGS.iter() { check(false, flags, 0, 0, 0, 1024); check(false, flags, 0, 0, 16, 0); check(true, flags, 1, 1, 1, 31); check(false, flags, 1, 32, 1, 1024); check(true, flags, 2, 1, 2, 29); check(false, flags, 2, 30, 2, 1024); check(true, flags, 3, 1, 3, 31); check(false, flags, 3, 32, 3, 1024); check(true, flags, 4, 1, 4, 30); check(false, flags, 4, 31, 4, 1024); check(true, flags, 5, 1, 5, 31); check(false, flags, 5, 32, 5, 1024); check(true, flags, 6, 1, 6, 30); check(false, flags, 6, 31, 6, 1024); check(true, flags, 7, 1, 7, 31); check(false, flags, 7, 32, 7, 1024); check(true, flags, 8, 1, 8, 31); check(false, flags, 8, 32, 8, 1024); check(true, flags, 9, 1, 9, 30); check(false, flags, 9, 31, 9, 1024); check(true, flags, 10, 1, 10, 31); check(false, flags, 10, 32, 10, 1024); check(true, flags, 11, 1, 11, 30); check(false, flags, 11, 31, 11, 1024); check(true, flags, 12, 1, 12, 31); check(false, flags, 12, 32, 12, 1024); check(false, flags, 13, 0, 16, 1024); check(false, flags, u32::MAX, 0, u32::MAX, 1024); check(false, flags, 0, u32::MAX, 16, u32::MAX); check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX); } } #[test] fn test_of_fields() { for &flags in FLAGS.iter() { for ordinal in 1u32..=366 { if let Some(of) = Of::new(ordinal, flags) { assert_eq!(of.ordinal(), ordinal); } } } } #[test] fn test_of_with_fields() { fn check(flags: YearFlags, ordinal: u32) { let of = Of::new(ordinal, flags).unwrap(); for ordinal in 0u32..=1024 { let of = of.with_ordinal(ordinal); assert_eq!(of, Of::new(ordinal, flags)); if let Some(of) = of { assert_eq!(of.ordinal(), ordinal); } } } for &flags in NONLEAP_FLAGS.iter() { check(flags, 1); check(flags, 365); } for &flags in LEAP_FLAGS.iter() { check(flags, 1); check(flags, 366); } } #[test] fn test_of_weekday() { assert_eq!(Of::new(1, A).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).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; } } } #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { 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); } } } } } #[test] fn test_mdf_with_fields() { fn check(flags: YearFlags, month: u32, day: u32) { 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), }; if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); } } 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); } } } for &flags in NONLEAP_FLAGS.iter() { check(flags, 1, 1); check(flags, 1, 31); check(flags, 2, 1); check(flags, 2, 28); check(flags, 2, 29); check(flags, 12, 31); } for &flags in LEAP_FLAGS.iter() { check(flags, 1, 1); check(flags, 1, 31); check(flags, 2, 1); check(flags, 2, 29); check(flags, 2, 30); check(flags, 12, 31); } } #[test] fn test_of_isoweekdate_raw() { for &flags in FLAGS.iter() { // January 4 should be in the first week let (week, _) = Of::new(4 /* January 4 */, flags).unwrap().isoweekdate_raw(); assert_eq!(week, 1); } } #[test] fn test_of_to_mdf() { 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 0u32..=8192 { let mdf = Mdf(i); assert_eq!(mdf.valid(), mdf.to_of().is_some()); } } #[test] fn test_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 0u32..=8192 { let mdf = Mdf(i); if mdf.valid() { 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); } } chrono-0.4.31/src/naive/isoweek.rs000064400000000000000000000157160072674642500151500ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! ISO 8601 week. use core::fmt; use super::internals::{DateImpl, Of, YearFlags}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 week. /// /// This type, combined with [`Weekday`](../enum.Weekday.html), /// 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, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] 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, // which year number might not match the calendar year number. ywf: DateImpl, // (year << 10) | (week << 4) | flag } /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. // // internal use only. we don't expose the public constructor for `IsoWeek` for now, // because the year range for the week date and the calendar date do not match and // it is confusing to have a date that is out of range in one and not in another. // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { let (rawweek, _) = of.isoweekdate_raw(); let (year, week) = if rawweek < 1 { // previous year let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); (year - 1, prevlastweek) } else { let lastweek = of.flags().nisoweeks(); if rawweek > lastweek { // next year (year + 1, 1) } else { (year, rawweek) } }; let flags = YearFlags::from_year(year); IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) } } impl IsoWeek { /// Returns the year number for this ISO week. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// /// 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_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.year(), 2014); /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` #[inline] pub const fn year(&self) -> i32 { self.ywf >> 10 } /// Returns the ISO week number starting from 1. /// /// The return value ranges from 1 to 53. (The last week of year differs by years.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week(), 15); /// ``` #[inline] pub const fn week(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 } /// Returns the ISO week number starting from 0. /// /// The return value ranges from 0 to 52. (The last week of year differs by years.) /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week0(), 14); /// ``` #[inline] pub const fn week0(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 - 1 } } /// The `Debug` output of the ISO week `w` is the same as /// [`d.format("%G-W%V")`](../format/strftime/index.html) /// where `d` is any `NaiveDate` value in that week. /// /// # Example /// /// ``` /// use chrono::{NaiveDate, Datelike}; /// /// 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_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..=9999).contains(&year) { write!(f, "{:04}-W{:02}", year, week) } else { // ISO 8601 requires the explicit sign for out-of-range years write!(f, "{:+05}-W{:02}", year, week) } } } #[cfg(test)] mod tests { use crate::naive::{internals, NaiveDate}; use crate::Datelike; #[test] fn test_iso_week_extremes() { 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); #[cfg(any(feature = "alloc", feature = "std"))] 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); #[cfg(any(feature = "alloc", feature = "std"))] 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()); } } chrono-0.4.31/src/naive/mod.rs000064400000000000000000000024540072674642500142540ustar 00000000000000//! 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. mod date; pub(crate) mod datetime; mod internals; mod isoweek; 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")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } chrono-0.4.31/src/naive/time/mod.rs000064400000000000000000001675470072674642500152310ustar 00000000000000// 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"))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, }; use crate::Timelike; use crate::{expect, try_opt}; #[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 + Duration` /// (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 - 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, 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(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveTime { secs: u32, frac: u32, } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NaiveTime { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let mins = u.int_in_range(0..=1439)?; let mut secs = u.int_in_range(0..=60)?; let mut nano = u.int_in_range(0..=999_999_999)?; if secs == 60 { secs = 59; nano += 1_000_000_000; } let time = NaiveTime::from_num_seconds_from_midnight_opt(mins * 60 + secs, nano) .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); Ok(time) } } impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// /// No [leap second](#leap-second-handling) is allowed here; /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. /// /// # Panics /// /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] #[inline] #[must_use] pub const fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { expect!(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveTime` from hour, minute and second. /// /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute and/or second. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let from_hms_opt = NaiveTime::from_hms_opt; /// /// assert!(from_hms_opt(0, 0, 0).is_some()); /// assert!(from_hms_opt(23, 59, 59).is_some()); /// assert!(from_hms_opt(24, 0, 0).is_none()); /// assert!(from_hms_opt(23, 60, 0).is_none()); /// assert!(from_hms_opt(23, 59, 60).is_none()); /// ``` #[inline] #[must_use] pub const fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { NaiveTime::from_hms_nano_opt(hour, min, sec, 0) } /// Makes a new `NaiveTime` from hour, minute, second and millisecond. /// /// The millisecond part can exceed 1,000 /// in order to represent the [leap second](#leap-second-handling). /// /// # Panics /// /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] #[inline] #[must_use] pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { expect!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and millisecond. /// /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute, second and/or millisecond. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let from_hmsm_opt = NaiveTime::from_hms_milli_opt; /// /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); /// ``` #[inline] #[must_use] pub const fn from_hms_milli_opt( hour: u32, min: u32, sec: u32, milli: u32, ) -> Option { let nano = try_opt!(milli.checked_mul(1_000_000)); NaiveTime::from_hms_nano_opt(hour, min, sec, nano) } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// /// Panics on invalid hour, minute, second and/or microsecond. #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] #[inline] #[must_use] pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { expect!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute, second and/or microsecond. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let from_hmsu_opt = NaiveTime::from_hms_micro_opt; /// /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); /// ``` #[inline] #[must_use] pub const fn from_hms_micro_opt( hour: u32, min: u32, sec: u32, micro: u32, ) -> Option { let nano = try_opt!(micro.checked_mul(1_000)); NaiveTime::from_hms_nano_opt(hour, min, sec, nano) } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] #[inline] #[must_use] pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { expect!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let from_hmsn_opt = NaiveTime::from_hms_nano_opt; /// /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); /// ``` #[inline] #[must_use] pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { if (hour >= 24 || min >= 60 || sec >= 60) || (nano >= 1_000_000_000 && sec != 59) || nano >= 2_000_000_000 { return None; } let secs = hour * 3600 + min * 60 + sec; Some(NaiveTime { secs, frac: nano }) } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Panics /// /// Panics on invalid number of seconds and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] #[inline] #[must_use] pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { expect!(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Errors /// /// Returns `None` on invalid number of seconds and/or nanosecond. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight_opt; /// /// assert!(from_nsecs_opt(0, 0).is_some()); /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 /// assert!(from_nsecs_opt(86_400, 0).is_none()); /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); /// ``` #[inline] #[must_use] pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { return None; } Some(NaiveTime { secs, frac: nano }) } /// Parses a string with the specified format string and returns a new `NaiveTime`. /// See the [`format::strftime` module](../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_opt(23, 56, 4).unwrap())); /// assert_eq!(parse_from_str("pm012345.6789", "%p%I%M%S%.f"), /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap())); /// ``` /// /// Date and offset is ignored for the purpose of parsing. /// /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap())); /// ``` /// /// [Leap seconds](#leap-second-handling) are correctly handled by /// treating any time of the form `hh:mm:60` as a leap second. /// (This equally applies to the formatting, so the round trip is possible.) /// /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("08:59:60.123", "%H:%M:%S%.f"), /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap())); /// ``` /// /// Missing seconds are assumed to be zero, /// but out-of-bound times or insufficient fields are errors otherwise. /// /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("7:15", "%H:%M"), /// Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("12", "%H").is_err()); /// assert!(parse_from_str("17:60", "%H:%M").is_err()); /// assert!(parse_from_str("24:00:00", "%H:%M:%S").is_err()); /// ``` /// /// All parsed fields should be consistent to each other, otherwise it's an error. /// Here `%H` is for 24-hour clocks, unlike `%I`, /// and thus can be independently determined without AM/PM. /// /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert!(parse_from_str("13:07 AM", "%H:%M %p").is_err()); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_naive_time() } /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with /// the remaining portion of the string. /// See the [`format::strftime` module](../format/strftime/index.html) /// 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 `Duration` to the current time, and also returns the number of *seconds* /// in the integral number of days ignored from the addition. /// /// # Example /// /// ``` /// use chrono::{Duration, 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(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)); /// ``` #[must_use] 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, 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..2 * 86_400).contains(&secs)); debug_assert!((0..1_000_000_000).contains(&frac)); if secs < 0 { secs += 86_400; morerhssecs -= 86_400; } else if secs >= 86_400 { secs -= 86_400; morerhssecs += 86_400; } debug_assert!((0..86_400).contains(&secs)); (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. /// /// # Example /// /// ``` /// use chrono::{Duration, 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(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] #[must_use] 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 /// /// ``` /// use chrono::{Duration, 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)), /// 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. /// /// ``` /// # use chrono::{Duration, 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)), /// 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)); /// ``` #[must_use] 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 => i64::from(rhs.frac >= 1_000_000_000), 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_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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, B: Borrow>, { DelayedFormat::new(None, Some(*self), items) } /// Formats the time with the specified format string. /// See the [`format::strftime` module](../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_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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Returns a triple of the hour, minute and second numbers. pub(crate) fn hms(&self) -> (u32, u32, u32) { let sec = self.secs % 60; let mins = self.secs / 60; let min = mins % 60; let hour = mins / 60; (hour, min, sec) } /// 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 { if hour >= 24 { return None; } let secs = hour * 3600 + self.secs % 3600; Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with the minute number changed. /// /// # Errors /// /// Returns `None` if the value for `minute` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap())); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] fn with_minute(&self, min: u32) -> Option { if min >= 60 { return None; } let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with the second number changed. /// /// As with the [`second`](#method.second) method, /// the input range is restricted to 0 through 59. /// /// # Errors /// /// Returns `None` if the value for `second` is invalid. /// /// # Example /// /// ``` /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap())); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] fn with_second(&self, sec: u32) -> Option { if sec >= 60 { return None; } let secs = self.secs / 60 * 60 + sec; Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. /// /// As with the [`nanosecond`](#method.nanosecond) method, /// the input range can exceed 1,000,000,000 for leap seconds. /// /// # Errors /// /// Returns `None` if `nanosecond >= 2,000,000,000`. /// /// # Example /// /// ``` /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` /// /// Leap seconds can theoretically follow *any* whole second. /// The following would be a proper leap second at the time zone offset of UTC-00:03:57 /// (there are several historical examples comparable to this "non-sense" offset), /// and therefore is allowed. /// /// ``` /// # use chrono::{NaiveTime, Timelike}; /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// let strange_leap_second = dt.with_nanosecond(1_333_333_333).unwrap(); /// assert_eq!(strange_leap_second.nanosecond(), 1_333_333_333); /// ``` #[inline] fn with_nanosecond(&self, nano: u32) -> Option { if nano >= 2_000_000_000 { return None; } Some(NaiveTime { frac: nano, ..*self }) } /// Returns the number of non-leap seconds past the last midnight. /// /// # Example /// /// ``` /// use chrono::{NaiveTime, Timelike}; /// /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), /// 3723); /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), /// 86164); /// assert_eq!(NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), /// 86399); /// ``` #[inline] fn num_seconds_from_midnight(&self) -> u32 { self.secs // do not repeat the calculation! } } /// 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], 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::{Duration, 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) + 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. /// /// ``` /// # use chrono::{Duration, 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) + 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. /// /// ``` /// # use chrono::{Duration, 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 + 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)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling impl Add for NaiveTime { type Output = NaiveTime; #[inline] fn add(self, rhs: OldDuration) -> NaiveTime { self.overflowing_add_signed(rhs).0 } } impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { *self = self.add(rhs); } } impl Add for NaiveTime { type Output = NaiveTime; #[inline] fn add(self, rhs: Duration) -> NaiveTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.overflowing_add_signed(rhs).0 } } impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: Duration) { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); *self += 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], 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::{Duration, 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) - 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. /// /// ``` /// # use chrono::{Duration, 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) - 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. /// /// ``` /// # use chrono::{Duration, 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 - 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)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling impl Sub for NaiveTime { type Output = NaiveTime; #[inline] fn sub(self, rhs: OldDuration) -> NaiveTime { self.overflowing_sub_signed(rhs).0 } } impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { *self = self.sub(rhs); } } impl Sub for NaiveTime { type Output = NaiveTime; #[inline] fn sub(self, rhs: Duration) -> NaiveTime { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); self.overflowing_sub_signed(rhs).0 } } impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { let rhs = OldDuration::from_std(rhs) .expect("overflow converting from core::time::Duration to chrono::Duration"); *self -= 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 /// /// ``` /// use chrono::{Duration, 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), 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. /// /// ``` /// # use chrono::{Duration, 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), Duration::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), /// Duration::milliseconds(1500)); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), Duration::seconds(60)); /// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), Duration::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), /// Duration::seconds(61)); /// ``` impl Sub for NaiveTime { type Output = OldDuration; #[inline] fn sub(self, rhs: NaiveTime) -> OldDuration { self.signed_duration_since(rhs) } } /// The `Debug` output of the naive time `t` is the same as /// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// /// It should be noted that, for leap seconds not on the minute boundary, /// it may print a representation not distinguishable from non-leap seconds. /// This doesn't matter in practice, since such leap seconds never happened. /// (By the time of the first leap second on 1972-06-30, /// every time zone offset around the world has standardized to the 5-minute alignment.) /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// assert_eq!(format!("{:?}", NaiveTime::from_hms_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")`](../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_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`](../format/strftime/index.html), as in `Debug` and `Display`. /// /// # Example /// /// ``` /// use chrono::NaiveTime; /// /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!("23:56:04".parse::(), Ok(t)); /// /// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); /// /// let t = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_234_567_890).unwrap(); // leap second /// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); /// /// // Seconds are optional /// let t = NaiveTime::from_hms_opt(23, 56, 0).unwrap(); /// assert_eq!("23:56".parse::(), Ok(t)); /// /// assert!("foo".parse::().is_err()); /// ``` impl str::FromStr for NaiveTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult { const HOUR_AND_MINUTE: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), ]; const SECOND_AND_NANOS: &[Item<'static>] = &[ Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), Item::Space(""), ]; const TRAILING_WHITESPACE: [Item<'static>; 1] = [Item::Space("")]; let mut parsed = Parsed::new(); let s = parse_and_remainder(&mut parsed, s, HOUR_AND_MINUTE.iter())?; // Seconds are optional, don't fail if parsing them doesn't succeed. let s = parse_and_remainder(&mut parsed, s, SECOND_AND_NANOS.iter()).unwrap_or(s); parse(&mut parsed, s, TRAILING_WHITESPACE.iter())?; parsed.to_naive_time() } } /// The default value for a NaiveTime is midnight, 00:00:00 exactly. /// /// # Example /// /// ```rust /// use chrono::NaiveTime; /// /// let default_time = NaiveTime::default(); /// assert_eq!(default_time, NaiveTime::from_hms_opt(0, 0, 0).unwrap()); /// ``` impl Default for NaiveTime { fn default() -> Self { NaiveTime::from_hms_opt(0, 0, 0).unwrap() } } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where F: Fn(&NaiveTime) -> Result, E: ::std::fmt::Debug, { assert_eq!( to_string(&NaiveTime::from_hms_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(from_str: F) where F: Fn(&str) -> Result, E: ::std::fmt::Debug, { assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_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()); } chrono-0.4.31/src/naive/time/rustc_serialize.rs000064400000000000000000000014140072674642500176350ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] use super::NaiveTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; impl Encodable for NaiveTime { fn encode(&self, s: &mut S) -> Result<(), S::Error> { format!("{:?}", self).encode(s) } } impl Decodable for NaiveTime { fn decode(d: &mut D) -> Result { d.read_str()?.parse().map_err(|_| d.error("invalid time")) } } #[cfg(test)] 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); } } chrono-0.4.31/src/naive/time/serde.rs000064400000000000000000000034760072674642500155420ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] use super::NaiveTime; use core::fmt; use serde::{de, ser}; // TODO not very optimized for space (binary formats would want something better) // TODO round-trip for general leap seconds (not just those with second = 60) impl ser::Serialize for NaiveTime { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.collect_str(&self) } } struct NaiveTimeVisitor; impl<'de> de::Visitor<'de> for NaiveTimeVisitor { type Value = NaiveTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a formatted time string") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(E::custom) } } impl<'de> de::Deserialize<'de> for NaiveTime { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(NaiveTimeVisitor) } } #[cfg(test)] 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); } } chrono-0.4.31/src/naive/time/tests.rs000064400000000000000000000315570072674642500156030ustar 00000000000000use super::NaiveTime; use crate::duration::Duration as OldDuration; use crate::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), OldDuration::zero(), hmsm(3, 5, 59, 900)); check!(hmsm(3, 5, 59, 900), OldDuration::milliseconds(100), hmsm(3, 6, 0, 0)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 58, 500)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 59, 500)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 59, 1_200)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 59, 1_400)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(800), hmsm(3, 6, 0, 100)); check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(1800), hmsm(3, 6, 1, 100)); check!(hmsm(3, 5, 59, 900), OldDuration::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap check!(hmsm(3, 5, 59, 900), OldDuration::seconds(-86399), hmsm(3, 6, 0, 900)); check!(hmsm(3, 5, 59, 900), OldDuration::days(12345), hmsm(3, 5, 59, 900)); check!(hmsm(3, 5, 59, 1_300), OldDuration::days(1), hmsm(3, 5, 59, 300)); check!(hmsm(3, 5, 59, 1_300), OldDuration::days(-1), hmsm(3, 6, 0, 300)); // regression tests for #37 check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-990), hmsm(23, 59, 59, 10)); check!(hmsm(0, 0, 0, 0), OldDuration::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(OldDuration::hours(11)), (hmsm(14, 4, 5, 678), 0) ); assert_eq!( hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(23)), (hmsm(2, 4, 5, 678), 86_400) ); assert_eq!( hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::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(OldDuration::days(1)), (hmsm(3, 4, 59, 678), 86_400) ); assert_eq!( hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::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 += OldDuration::hours(10); assert_eq!(time, hms(22, 12, 12)); time += OldDuration::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 -= OldDuration::hours(10); assert_eq!(time, hms(2, 12, 12)); time -= OldDuration::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), OldDuration::zero()); check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), OldDuration::milliseconds(300)); check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), OldDuration::seconds(3600 + 60 + 1)); check!( hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 300), OldDuration::seconds(3600 + 60) + OldDuration::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), OldDuration::milliseconds(400)); //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::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) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); //assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::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::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; let s_ = format!("{:?}", d); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ `{:?}` does not match", s, d, d_ ); } // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ "", // empty "x", // invalid "15", // missing data "15:8:", // trailing colon "15:8:x", // invalid data "15:8:9x", // invalid data "23:59:61", // invalid second (out of bounds) "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) "1441497364.649", // valid datetime, not a NaiveTime "+1441497364.649", // valid datetime, not a NaiveTime "+1441497364", // valid datetime, not a NaiveTime "001:02:03", // invalid hour "01:002:03", // invalid minute "01:02:003", // invalid second "12:34:56.x", // invalid fraction "12:34:56. 0", // invalid fraction format "09:08:00000000007", // invalid second / invalid fraction format ]; for &s in &invalid { eprintln!("test_time_parse_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } } #[test] fn test_time_parse_from_str() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); assert_eq!( NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); assert_eq!( NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), Ok(hms(12, 59, 0)) ); assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } chrono-0.4.31/src/offset/fixed.rs000064400000000000000000000226750072674642500147670ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! The time zone which has a fixed offset from UTC. use core::fmt; use core::ops::{Add, Sub}; use core::str::FromStr; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; use crate::duration::Duration as OldDuration; use crate::format::{scan, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on a `FixedOffset` struct is the preferred way to construct /// `DateTime` instances. See the [`east_opt`](#method.east_opt) and /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct FixedOffset { local_minus_utc: i32, } impl FixedOffset { /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. /// The negative `secs` means the Western Hemisphere. /// /// Panics on the out-of-bound `secs`. #[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") } /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. /// The negative `secs` means the Western Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. /// /// # 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 { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) } else { None } } /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. /// The negative `secs` means the Eastern Hemisphere. /// /// Panics on the out-of-bound `secs`. #[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") } /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. /// The negative `secs` means the Eastern Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. /// /// # 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 { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) } else { None } } /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] 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 const fn utc_minus_local(&self) -> i32 { -self.local_minus_utc } } /// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). impl FromStr for FixedOffset { type Err = ParseError; fn from_str(s: &str) -> Result { let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; Self::east_opt(offset).ok_or(OUT_OF_RANGE) } } impl TimeZone for FixedOffset { type Offset = FixedOffset; fn from_offset(offset: &FixedOffset) -> FixedOffset { *offset } fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { LocalResult::Single(*self) } fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { LocalResult::Single(*self) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { *self } fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { *self } } impl Offset for FixedOffset { fn fix(&self) -> FixedOffset { *self } } 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 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 { write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) } } } impl fmt::Display for FixedOffset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for FixedOffset { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let secs = u.int_in_range(-86_399..=86_399)?; let fixed_offset = FixedOffset::east_opt(secs) .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); Ok(fixed_offset) } } // addition or subtraction of FixedOffset to/from Timelike values is the same as // adding or subtracting the offset's local_minus_utc value // but keep keeps the leap second information. // this should be implemented more efficiently, but for the time being, this is generic right now. fn add_with_leapsecond(lhs: &T, rhs: i32) -> T where T: Timelike + Add, { // extract and temporarily remove the fractional part and later recover it let nanos = lhs.nanosecond(); let lhs = lhs.with_nanosecond(0).unwrap(); (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() } impl Add for NaiveTime { type Output = NaiveTime; #[inline] fn add(self, rhs: FixedOffset) -> NaiveTime { add_with_leapsecond(&self, rhs.local_minus_utc) } } impl Sub for NaiveTime { type Output = NaiveTime; #[inline] fn sub(self, rhs: FixedOffset) -> NaiveTime { add_with_leapsecond(&self, -rhs.local_minus_utc) } } impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { add_with_leapsecond(&self, rhs.local_minus_utc) } } impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { add_with_leapsecond(&self, -rhs.local_minus_utc) } } impl Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: FixedOffset) -> DateTime { add_with_leapsecond(&self, rhs.local_minus_utc) } } impl Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: FixedOffset) -> DateTime { add_with_leapsecond(&self, -rhs.local_minus_utc) } } #[cfg(test)] mod tests { use super::FixedOffset; 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!("{:?}", 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!("{:?}", 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!("{:?}", 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!("{:?}", 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); } } chrono-0.4.31/src/offset/local/mod.rs000064400000000000000000000206360072674642500155340ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! The local (system) time zone. #[cfg(feature = "rkyv")] 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 { LocalResult::Single(FixedOffset::east_opt(0).unwrap()) } pub(super) fn offset_from_local_datetime( _local_time: &NaiveDateTime, ) -> LocalResult { 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 { 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 { 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. This is implemented via the standard `time` crate. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on the Local struct is the preferred way to construct `DateTime` /// instances. /// /// # Example /// /// ``` /// use chrono::{Local, DateTime, TimeZone}; /// /// let dt1: DateTime = Local::now(); /// let dt2: DateTime = Local.timestamp_opt(0, 0).unwrap(); /// assert!(dt1 >= dt2); /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[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::now().date() } /// Returns a `DateTime` which corresponds to the current date, time and offset from /// UTC. /// /// See also the similar [`Utc::now()`] which returns `DateTime`, i.e. without the local /// offset. /// /// # Example /// /// ``` /// # #![allow(unused_variables)] /// # use chrono::{DateTime, FixedOffset, Local}; /// // Current local time /// let now = Local::now(); /// /// // Current local date /// let today = now.date_naive(); /// /// // Current local time, converted to `DateTime` /// let now_fixed_offset = Local::now().fixed_offset(); /// // or /// let now_fixed_offset: DateTime = Local::now().into(); /// /// // Current time in some timezone (let's use +05:00) /// // Note that it is usually more efficient to use `Utc::now` for this use case. /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); /// let now_with_offset = Local::now().with_timezone(&offset); /// ``` pub fn now() -> DateTime { Utc::now().with_timezone(&Local) } } impl TimeZone for Local { type Offset = FixedOffset; fn from_offset(_offset: &FixedOffset) -> Local { Local } #[allow(deprecated)] fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { // 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 { 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(test)] mod tests { use super::Local; use crate::offset::TimeZone; use crate::{Datelike, Duration, Utc}; #[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() - Duration::days(365 * 100); let distant_past = Local::now() - Duration::days(250 * 31); 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() + Duration::days(250 * 31); 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 ); } } } chrono-0.4.31/src/offset/local/tz_info/mod.rs000064400000000000000000000066530072674642500172070ustar 00000000000000#![deny(missing_docs)] #![allow(dead_code)] #![warn(unreachable_pub)] use std::num::ParseIntError; use std::str::Utf8Error; use std::time::SystemTimeError; use std::{error, fmt, io}; mod timezone; pub(crate) use timezone::TimeZone; mod parser; mod rule; /// Unified error type for everything in the crate #[derive(Debug)] pub(crate) enum Error { /// Date time error DateTime(&'static str), /// Local time type search error FindLocalTimeType(&'static str), /// Local time type error LocalTimeType(&'static str), /// Invalid slice for integer conversion InvalidSlice(&'static str), /// Invalid Tzif file InvalidTzFile(&'static str), /// Invalid TZ string InvalidTzString(&'static str), /// I/O error Io(io::Error), /// Out of range error OutOfRange(&'static str), /// Integer parsing error ParseInt(ParseIntError), /// Date time projection error ProjectDateTime(&'static str), /// System time error SystemTime(SystemTimeError), /// Time zone error TimeZone(&'static str), /// Transition rule error TransitionRule(&'static str), /// Unsupported Tzif file UnsupportedTzFile(&'static str), /// Unsupported TZ string UnsupportedTzString(&'static str), /// UTF-8 error Utf8(Utf8Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { DateTime(error) => write!(f, "invalid date time: {}", error), FindLocalTimeType(error) => error.fmt(f), LocalTimeType(error) => write!(f, "invalid local time type: {}", error), InvalidSlice(error) => error.fmt(f), InvalidTzString(error) => write!(f, "invalid TZ string: {}", error), InvalidTzFile(error) => error.fmt(f), Io(error) => error.fmt(f), OutOfRange(error) => error.fmt(f), ParseInt(error) => error.fmt(f), ProjectDateTime(error) => error.fmt(f), SystemTime(error) => error.fmt(f), TransitionRule(error) => write!(f, "invalid transition rule: {}", error), TimeZone(error) => write!(f, "invalid time zone: {}", error), UnsupportedTzFile(error) => error.fmt(f), UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error), Utf8(error) => error.fmt(f), } } } impl error::Error for Error {} impl From for Error { fn from(error: io::Error) -> Self { Error::Io(error) } } impl From for Error { fn from(error: ParseIntError) -> Self { Error::ParseInt(error) } } impl From for Error { fn from(error: SystemTimeError) -> Self { Error::SystemTime(error) } } impl From for Error { fn from(error: Utf8Error) -> Self { Error::Utf8(error) } } /// Number of hours in one day const HOURS_PER_DAY: i64 = 24; /// Number of seconds in one hour const SECONDS_PER_HOUR: i64 = 3600; /// Number of seconds in one day const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY; /// Number of days in one week const DAYS_PER_WEEK: i64 = 7; /// Month days in a normal year const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /// Cumulated month days in a normal year const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; chrono-0.4.31/src/offset/local/tz_info/parser.rs000064400000000000000000000261300072674642500177140ustar 00000000000000use 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 { let mut cursor = Cursor::new(bytes); let state = State::new(&mut cursor, true)?; let (state, footer) = match state.header.version { Version::V1 => match cursor.is_empty() { true => (state, None), false => { return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")) } }, Version::V2 | Version::V3 => { let state = State::new(&mut cursor, false)?; (state, Some(cursor.remaining())) } }; let mut transitions = Vec::with_capacity(state.header.transition_count); for (arr_time, &local_time_type_index) in state.transition_times.chunks_exact(state.time_size).zip(state.transition_types) { let unix_leap_time = state.parse_time(&arr_time[0..state.time_size], state.header.version)?; let local_time_type_index = local_time_type_index as usize; transitions.push(Transition::new(unix_leap_time, local_time_type_index)); } let mut local_time_types = Vec::with_capacity(state.header.type_count); for arr in state.local_time_types.chunks_exact(6) { let ut_offset = read_be_i32(&arr[..4])?; let is_dst = match arr[4] { 0 => false, 1 => true, _ => return Err(Error::InvalidTzFile("invalid DST indicator")), }; let char_index = arr[5] as usize; if char_index >= state.header.char_count { return Err(Error::InvalidTzFile("invalid time zone name char index")); } let position = match state.names[char_index..].iter().position(|&c| c == b'\0') { Some(position) => position, None => return Err(Error::InvalidTzFile("invalid time zone name char index")), }; let name = &state.names[char_index..char_index + position]; let name = if !name.is_empty() { Some(name) } else { None }; local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?); } let mut leap_seconds = Vec::with_capacity(state.header.leap_count); for arr in state.leap_seconds.chunks_exact(state.time_size + 4) { let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?; let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?; leap_seconds.push(LeapSecond::new(unix_leap_time, correction)); } let std_walls_iter = state.std_walls.iter().copied().chain(iter::repeat(0)); let ut_locals_iter = state.ut_locals.iter().copied().chain(iter::repeat(0)); if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) { return Err(Error::InvalidTzFile( "invalid couple of standard/wall and UT/local indicators", )); } let extra_rule = match footer { Some(footer) => { let footer = str::from_utf8(footer)?; if !(footer.starts_with('\n') && footer.ends_with('\n')) { return Err(Error::InvalidTzFile("invalid footer")); } let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace()); if tz_string.starts_with(':') || tz_string.contains('\0') { return Err(Error::InvalidTzFile("invalid footer")); } match tz_string.is_empty() { true => None, false => Some(TransitionRule::from_tz_string( tz_string.as_bytes(), state.header.version == Version::V3, )?), } } None => None, }; TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule) } /// TZif data blocks struct State<'a> { header: Header, /// Time size in bytes time_size: usize, /// Transition times data block transition_times: &'a [u8], /// Transition types data block transition_types: &'a [u8], /// Local time types data block local_time_types: &'a [u8], /// Time zone names data block names: &'a [u8], /// Leap seconds data block leap_seconds: &'a [u8], /// UT/local indicators data block std_walls: &'a [u8], /// Standard/wall indicators data block ut_locals: &'a [u8], } impl<'a> State<'a> { /// Read TZif data blocks fn new(cursor: &mut Cursor<'a>, first: bool) -> Result { let header = Header::new(cursor)?; let time_size = match first { true => 4, // We always parse V1 first false => 8, }; Ok(Self { time_size, transition_times: cursor.read_exact(header.transition_count * time_size)?, transition_types: cursor.read_exact(header.transition_count)?, local_time_types: cursor.read_exact(header.type_count * 6)?, names: cursor.read_exact(header.char_count)?, leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?, std_walls: cursor.read_exact(header.std_wall_count)?, ut_locals: cursor.read_exact(header.ut_local_count)?, header, }) } /// Parse time values fn parse_time(&self, arr: &[u8], version: Version) -> Result { match version { Version::V1 => Ok(read_be_i32(&arr[..4])?.into()), Version::V2 | Version::V3 => read_be_i64(arr), } } } /// TZif header #[derive(Debug)] struct Header { /// TZif version version: Version, /// Number of UT/local indicators ut_local_count: usize, /// Number of standard/wall indicators std_wall_count: usize, /// Number of leap-second records leap_count: usize, /// Number of transition times transition_count: usize, /// Number of local time type records type_count: usize, /// Number of time zone names bytes char_count: usize, } impl Header { fn new(cursor: &mut Cursor) -> Result { let magic = cursor.read_exact(4)?; if magic != *b"TZif" { return Err(Error::InvalidTzFile("invalid magic number")); } let version = match cursor.read_exact(1)? { [0x00] => Version::V1, [0x32] => Version::V2, [0x33] => Version::V3, _ => return Err(Error::UnsupportedTzFile("unsupported TZif version")), }; cursor.read_exact(15)?; let ut_local_count = cursor.read_be_u32()?; let std_wall_count = cursor.read_be_u32()?; let leap_count = cursor.read_be_u32()?; let transition_count = cursor.read_be_u32()?; let type_count = cursor.read_be_u32()?; let char_count = cursor.read_be_u32()?; if !(type_count != 0 && char_count != 0 && (ut_local_count == 0 || ut_local_count == type_count) && (std_wall_count == 0 || std_wall_count == type_count)) { return Err(Error::InvalidTzFile("invalid header")); } Ok(Self { version, ut_local_count: ut_local_count as usize, std_wall_count: std_wall_count as usize, leap_count: leap_count as usize, transition_count: transition_count as usize, type_count: type_count as usize, char_count: char_count as usize, }) } } /// A `Cursor` contains a slice of a buffer and a read count. #[derive(Debug, Eq, PartialEq)] pub(crate) struct Cursor<'a> { /// Slice representing the remaining data to be read remaining: &'a [u8], /// Number of already read bytes read_count: usize, } impl<'a> Cursor<'a> { /// Construct a new `Cursor` from remaining data pub(crate) const fn new(remaining: &'a [u8]) -> Self { Self { remaining, read_count: 0 } } pub(crate) fn peek(&self) -> Option<&u8> { self.remaining().first() } /// Returns remaining data pub(crate) const fn remaining(&self) -> &'a [u8] { self.remaining } /// Returns `true` if data is remaining pub(crate) const fn is_empty(&self) -> bool { self.remaining.is_empty() } pub(crate) fn read_be_u32(&mut self) -> Result { let mut buf = [0; 4]; buf.copy_from_slice(self.read_exact(4)?); Ok(u32::from_be_bytes(buf)) } /// Read exactly `count` bytes, reducing remaining data and incrementing read count pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> { match (self.remaining.get(..count), self.remaining.get(count..)) { (Some(result), Some(remaining)) => { self.remaining = remaining; self.read_count += count; Ok(result) } _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), } } /// Read bytes and compare them to the provided tag pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> { if self.read_exact(tag.len())? == tag { Ok(()) } else { Err(io::Error::from(ErrorKind::InvalidData)) } } /// Read bytes if the remaining data is prefixed by the provided tag pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result { if self.remaining.starts_with(tag) { self.read_exact(tag.len())?; Ok(true) } else { Ok(false) } } /// Read bytes as long as the provided predicate is true pub(crate) fn read_while bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { match self.remaining.iter().position(|x| !f(x)) { None => self.read_exact(self.remaining.len()), Some(position) => self.read_exact(position), } } // Parse an integer out of the ASCII digits pub(crate) fn read_int>(&mut self) -> Result { let bytes = self.read_while(u8::is_ascii_digit)?; Ok(str::from_utf8(bytes)?.parse()?) } /// Read bytes until the provided predicate is true pub(crate) fn read_until bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { match self.remaining.iter().position(f) { None => self.read_exact(self.remaining.len()), Some(position) => self.read_exact(position), } } } pub(crate) fn read_be_i32(bytes: &[u8]) -> Result { if bytes.len() != 4 { return Err(Error::InvalidSlice("too short for i32")); } let mut buf = [0; 4]; buf.copy_from_slice(bytes); Ok(i32::from_be_bytes(buf)) } pub(crate) fn read_be_i64(bytes: &[u8]) -> Result { if bytes.len() != 8 { return Err(Error::InvalidSlice("too short for i64")); } let mut buf = [0; 8]; buf.copy_from_slice(bytes); Ok(i64::from_be_bytes(buf)) } /// TZif version #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum Version { /// Version 1 V1, /// Version 2 V2, /// Version 3 V3, } chrono-0.4.31/src/offset/local/tz_info/rule.rs000064400000000000000000001152510072674642500173720ustar 00000000000000use 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 { 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, 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 for TransitionRule { fn from(inner: LocalTimeType) -> Self { TransitionRule::Fixed(inner) } } impl From for TransitionRule { fn from(inner: AlternateTime) -> Self { TransitionRule::Alternate(inner) } } /// Transition rule representing alternate local time types #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(super) struct AlternateTime { /// Local time type for standard time pub(super) std: LocalTimeType, /// Local time type for Daylight Saving Time pub(super) dst: LocalTimeType, /// Start day of Daylight Saving Time dst_start: RuleDay, /// Local start day time of Daylight Saving Time, in seconds dst_start_time: i32, /// End day of Daylight Saving Time dst_end: RuleDay, /// Local end day time of Daylight Saving Time, in seconds dst_end_time: i32, } impl AlternateTime { /// Construct a transition rule representing alternate local time types const fn new( std: LocalTimeType, dst: LocalTimeType, dst_start: RuleDay, dst_start_time: i32, dst_end: RuleDay, dst_end_time: i32, ) -> Result { // Overflow is not possible if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) { return Err(Error::TransitionRule("invalid DST start or end time")); } Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time }) } /// Find the local time type associated to the alternate transition rule at the specified Unix time in seconds fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { // Overflow is not possible let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; let current_year = 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(¤t_year_dst_start_unix_time, ¤t_year_dst_end_unix_time) { Ordering::Less | Ordering::Equal => { if unix_time < current_year_dst_start_unix_time { let previous_year_dst_end_unix_time = self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); if unix_time < previous_year_dst_end_unix_time { let previous_year_dst_start_unix_time = self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); previous_year_dst_start_unix_time <= unix_time } else { false } } else if unix_time < current_year_dst_end_unix_time { true } else { let next_year_dst_start_unix_time = self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); if next_year_dst_start_unix_time <= unix_time { let next_year_dst_end_unix_time = self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); unix_time < next_year_dst_end_unix_time } else { false } } } Ordering::Greater => { if unix_time < current_year_dst_end_unix_time { let previous_year_dst_start_unix_time = self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); if unix_time < previous_year_dst_start_unix_time { let previous_year_dst_end_unix_time = self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); unix_time < previous_year_dst_end_unix_time } else { true } } else if unix_time < current_year_dst_start_unix_time { false } else { let next_year_dst_end_unix_time = self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); if next_year_dst_end_unix_time <= unix_time { let next_year_dst_start_unix_time = self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); next_year_dst_start_unix_time <= unix_time } else { true } } } }; if is_dst { Ok(&self.dst) } else { Ok(&self.std) } } fn find_local_time_type_from_local( &self, local_time: i64, current_year: i32, ) -> Result, 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 { 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 { let (hour, minute, second) = parse_hhmmss(cursor)?; if !(0..=24).contains(&hour) { return Err(Error::InvalidTzString("invalid day time hour")); } if !(0..=59).contains(&minute) { return Err(Error::InvalidTzString("invalid day time minute")); } if !(0..=59).contains(&second) { return Err(Error::InvalidTzString("invalid day time second")); } Ok(hour * 3600 + minute * 60 + second) } /// Parse transition rule time with TZ string extensions fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; if !(-167..=167).contains(&hour) { return Err(Error::InvalidTzString("invalid day time hour")); } if !(0..=59).contains(&minute) { return Err(Error::InvalidTzString("invalid day time minute")); } if !(0..=59).contains(&second) { return Err(Error::InvalidTzString("invalid day time second")); } Ok(sign * (hour * 3600 + minute * 60 + second)) } /// Parse hours, minutes and seconds fn parse_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32), Error> { let hour = cursor.read_int()?; let mut minute = 0; let mut second = 0; if cursor.read_optional_tag(b":")? { minute = cursor.read_int()?; if cursor.read_optional_tag(b":")? { second = cursor.read_int()?; } } Ok((hour, minute, second)) } /// Parse signed hours, minutes and seconds fn parse_signed_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32, i32), Error> { let mut sign = 1; if let Some(&c) = cursor.peek() { if c == b'+' || c == b'-' { cursor.read_exact(1)?; if c == b'-' { sign = -1; } } } let (hour, minute, second) = parse_hhmmss(cursor)?; Ok((sign, hour, minute, second)) } /// Transition rule day #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum RuleDay { /// Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable Julian1WithoutLeap(u16), /// Zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account Julian0WithLeap(u16), /// Day represented by a month, a month week and a week day MonthWeekday { /// Month in `[1, 12]` month: u8, /// Week of the month in `[1, 5]`, with `5` representing the last week of the month week: u8, /// Day of the week in `[0, 6]` from Sunday week_day: u8, }, } impl RuleDay { /// Parse transition rule fn parse(cursor: &mut Cursor, use_string_extensions: bool) -> Result<(Self, i32), Error> { let date = match cursor.peek() { Some(b'M') => { cursor.read_exact(1)?; let month = cursor.read_int()?; cursor.read_tag(b".")?; let week = cursor.read_int()?; cursor.read_tag(b".")?; let week_day = cursor.read_int()?; RuleDay::month_weekday(month, week, week_day)? } Some(b'J') => { cursor.read_exact(1)?; RuleDay::julian_1(cursor.read_int()?)? } _ => RuleDay::julian_0(cursor.read_int()?)?, }; Ok(( date, match (cursor.read_optional_tag(b"/")?, use_string_extensions) { (false, _) => 2 * 3600, (true, true) => parse_rule_time_extended(cursor)?, (true, false) => parse_rule_time(cursor)?, }, )) } /// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable fn julian_1(julian_day_1: u16) -> Result { if !(1..=365).contains(&julian_day_1) { return Err(Error::TransitionRule("invalid rule day julian day")); } Ok(RuleDay::Julian1WithoutLeap(julian_day_1)) } /// Construct a transition rule day represented by a zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account const fn julian_0(julian_day_0: u16) -> Result { if julian_day_0 > 365 { return Err(Error::TransitionRule("invalid rule day julian day")); } Ok(RuleDay::Julian0WithLeap(julian_day_0)) } /// Construct a transition rule day represented by a month, a month week and a week day fn month_weekday(month: u8, week: u8, week_day: u8) -> Result { if !(1..=12).contains(&month) { return Err(Error::TransitionRule("invalid rule day month")); } if !(1..=5).contains(&week) { return Err(Error::TransitionRule("invalid rule day week")); } if week_day > 6 { return Err(Error::TransitionRule("invalid rule day week day")); } Ok(RuleDay::MonthWeekday { month, week, week_day }) } /// Get the transition date for the provided year /// /// ## Outputs /// /// * `month`: Month in `[1, 12]` /// * `month_day`: Day of the month in `[1, 31]` fn transition_date(&self, year: i32) -> (usize, i64) { match *self { RuleDay::Julian1WithoutLeap(year_day) => { let year_day = year_day as i64; let month = match CUMUL_DAY_IN_MONTHS_NORMAL_YEAR.binary_search(&(year_day - 1)) { Ok(x) => x + 1, Err(x) => x, }; let month_day = year_day - CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; (month, month_day) } RuleDay::Julian0WithLeap(year_day) => { let leap = is_leap_year(year) as i64; let cumul_day_in_months = [ 0, 31, 59 + leap, 90 + leap, 120 + leap, 151 + leap, 181 + leap, 212 + leap, 243 + leap, 273 + leap, 304 + leap, 334 + leap, ]; let year_day = year_day as i64; let month = match cumul_day_in_months.binary_search(&year_day) { Ok(x) => x + 1, Err(x) => x, }; let month_day = 1 + year_day - cumul_day_in_months[month - 1]; (month, month_day) } RuleDay::MonthWeekday { month: rule_month, week, week_day } => { let leap = is_leap_year(year) as i64; let month = rule_month as usize; let mut day_in_month = DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; if month == 2 { day_in_month += leap; } let week_day_of_first_month_day = (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK); let first_week_day_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 { 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(()) } } chrono-0.4.31/src/offset/local/tz_info/timezone.rs000064400000000000000000001101730072674642500202530ustar 00000000000000//! 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, /// List of local time types (cannot be empty) local_time_types: Vec, /// List of leap seconds leap_seconds: Vec, /// Extra transition rule applicable after the last transition extra_rule: Option, } impl TimeZone { /// Returns local time zone. /// /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. /// pub(crate) fn local(env_tz: Option<&str>) -> Result { match env_tz { Some(tz) => Self::from_posix_tz(tz), None => Self::from_posix_tz("localtime"), } } /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). fn from_posix_tz(tz_string: &str) -> Result { 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, local_time_types: Vec, leap_seconds: Vec, extra_rule: Option, ) -> Result { let new = Self { transitions, local_time_types, leap_seconds, extra_rule }; new.as_ref().validate()?; Ok(new) } /// Construct a time zone from the contents of a time zone file fn from_file(file: &mut File) -> Result { let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; Self::from_tz_data(&bytes) } /// Construct a time zone from the contents of a time zone file /// /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). pub(crate) fn from_tz_data(bytes: &[u8]) -> Result { parser::parse(bytes) } /// Construct a time zone with the specified UTC offset in seconds fn fixed(ut_offset: i32) -> Result { Ok(Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::with_offset(ut_offset)?], leap_seconds: Vec::new(), extra_rule: None, }) } /// Construct the time zone associated to UTC pub(crate) fn utc() -> Self { Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::UTC], leap_seconds: Vec::new(), extra_rule: None, } } /// Find the local time type associated to the time zone at the specified Unix time in seconds pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { self.as_ref().find_local_time_type(unix_time) } // 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, 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, } 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, 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 { let mut unix_leap_time = unix_time; let mut i = 0; while i < self.leap_seconds.len() { let leap_second = &self.leap_seconds[i]; if unix_leap_time < leap_second.unix_leap_time { break; } unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) { Some(unix_leap_time) => unix_leap_time, None => return Err(Error::OutOfRange("out of range operation")), }; i += 1; } Ok(unix_leap_time) } /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { if unix_leap_time == i64::min_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 { 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 for TimeZoneName { fn as_ref(&self) -> &str { // SAFETY: ASCII is valid UTF-8 unsafe { str::from_utf8_unchecked(self.as_bytes()) } } } impl fmt::Debug for TimeZoneName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_ref().fmt(f) } } /// Local time type associated to a time zone #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) struct LocalTimeType { /// Offset from UTC in seconds pub(super) ut_offset: i32, /// Daylight Saving Time indicator is_dst: bool, /// Time zone name name: Option, } impl LocalTimeType { /// Construct a local time type pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result { if ut_offset == i32::min_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 { 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) -> Result { // Don't check system timezone directories on non-UNIX platforms #[cfg(not(unix))] return Ok(File::open(path)?); #[cfg(unix)] { let path = path.as_ref(); if path.is_absolute() { return Ok(File::open(path)?); } for folder in &ZONE_INFO_DIRECTORIES { if let Ok(file) = File::open(PathBuf::from(folder).join(path)) { return Ok(file); } } Err(Error::Io(io::ErrorKind::NotFound.into())) } } // Possible system timezone directories #[cfg(unix)] const ZONE_INFO_DIRECTORIES: [&str; 4] = ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"]; /// Number of seconds in one week pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK; /// Number of seconds in 28 days const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; #[cfg(test)] mod tests { use super::super::Error; use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; #[test] fn test_no_dst() -> Result<(), Error> { let tz_string = b"HST10"; let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into()); Ok(()) } #[test] fn test_error() -> Result<(), Error> { assert!(matches!( TransitionRule::from_tz_string(b"IST-1GMT0", false), Err(Error::UnsupportedTzString(_)) )); assert!(matches!( TransitionRule::from_tz_string(b"EET-2EEST", false), Err(Error::UnsupportedTzString(_)) )); Ok(()) } #[test] fn test_v1_file_with_leap_seconds() -> Result<(), Error> { let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0"; let time_zone = TimeZone::from_tz_data(bytes)?; let time_zone_result = TimeZone::new( Vec::new(), vec![LocalTimeType::new(0, false, Some(b"UTC"))?], vec![ LeapSecond::new(78796800, 1), LeapSecond::new(94694401, 2), LeapSecond::new(126230402, 3), LeapSecond::new(157766403, 4), LeapSecond::new(189302404, 5), LeapSecond::new(220924805, 6), LeapSecond::new(252460806, 7), LeapSecond::new(283996807, 8), LeapSecond::new(315532808, 9), LeapSecond::new(362793609, 10), LeapSecond::new(394329610, 11), LeapSecond::new(425865611, 12), LeapSecond::new(489024012, 13), LeapSecond::new(567993613, 14), LeapSecond::new(631152014, 15), LeapSecond::new(662688015, 16), LeapSecond::new(709948816, 17), LeapSecond::new(741484817, 18), LeapSecond::new(773020818, 19), LeapSecond::new(820454419, 20), LeapSecond::new(867715220, 21), LeapSecond::new(915148821, 22), LeapSecond::new(1136073622, 23), LeapSecond::new(1230768023, 24), LeapSecond::new(1341100824, 25), LeapSecond::new(1435708825, 26), LeapSecond::new(1483228826, 27), ], None, )?; assert_eq!(time_zone, time_zone_result); Ok(()) } #[test] fn test_v2_file() -> Result<(), Error> { let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a"; let time_zone = TimeZone::from_tz_data(bytes)?; let time_zone_result = TimeZone::new( vec![ Transition::new(-2334101314, 1), Transition::new(-1157283000, 2), Transition::new(-1155436200, 1), Transition::new(-880198200, 3), Transition::new(-769395600, 4), Transition::new(-765376200, 1), Transition::new(-712150200, 5), ], vec![ LocalTimeType::new(-37886, false, Some(b"LMT"))?, LocalTimeType::new(-37800, false, Some(b"HST"))?, LocalTimeType::new(-34200, true, Some(b"HDT"))?, LocalTimeType::new(-34200, true, Some(b"HWT"))?, LocalTimeType::new(-34200, true, Some(b"HPT"))?, LocalTimeType::new(-36000, false, Some(b"HST"))?, ], Vec::new(), Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)), )?; assert_eq!(time_zone, time_zone_result); assert_eq!( *time_zone.find_local_time_type(-1156939200)?, LocalTimeType::new(-34200, true, Some(b"HDT"))? ); assert_eq!( *time_zone.find_local_time_type(1546300800)?, LocalTimeType::new(-36000, false, Some(b"HST"))? ); Ok(()) } #[test] fn test_no_tz_string() -> Result<(), Error> { // Guayaquil from macOS 10.11 let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x01\0\0\0\x02\0\0\0\x08\xb6\xa4B\x18\x01\xff\xff\xb6h\0\0\xff\xff\xb9\xb0\0\x04QMT\0ECT\0\0\0\0\0"; let time_zone = TimeZone::from_tz_data(bytes)?; dbg!(&time_zone); let time_zone_result = TimeZone::new( vec![Transition::new(-1230749160, 1)], vec![ LocalTimeType::new(-18840, false, Some(b"QMT"))?, LocalTimeType::new(-18000, false, Some(b"ECT"))?, ], Vec::new(), None, )?; assert_eq!(time_zone, time_zone_result); assert_eq!( *time_zone.find_local_time_type(-1500000000)?, LocalTimeType::new(-18840, false, Some(b"QMT"))? ); assert_eq!( *time_zone.find_local_time_type(0)?, LocalTimeType::new(-18000, false, Some(b"ECT"))? ); Ok(()) } #[test] fn test_tz_ascii_str() -> Result<(), Error> { assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_)))); assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_)))); assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET"); assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT"); assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg"); assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02"); assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); 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(()) } } chrono-0.4.31/src/offset/local/unix.rs000064400000000000000000000144200072674642500157320ustar 00000000000000// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; use super::tz_info::TimeZone; use super::{FixedOffset, NaiveDateTime}; use crate::{Datelike, LocalResult}; pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { offset(utc, false) } pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { offset(local, true) } fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { 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> = 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 { 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 { 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()) } } chrono-0.4.31/src/offset/local/win_bindings.rs000064400000000000000000000033150072674642500174220ustar 00000000000000// Bindings generated by `windows-bindgen` 0.51.1 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] ::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; #[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 } } chrono-0.4.31/src/offset/local/win_bindings.txt000064400000000000000000000003640072674642500176160ustar 00000000000000--out src/offset/local/win_bindings.rs --config flatten sys --filter Windows.Win32.System.Time.SystemTimeToFileTime Windows.Win32.System.Time.SystemTimeToTzSpecificLocalTime Windows.Win32.System.Time.TzSpecificLocalTimeToSystemTime chrono-0.4.31/src/offset/local/windows.rs000064400000000000000000000121550072674642500164440ustar 00000000000000// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::mem::MaybeUninit; use std::io::Error; use std::ptr; use std::result::Result; use super::win_bindings::{ SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, FILETIME, SYSTEMTIME, }; use super::FixedOffset; use crate::{Datelike, LocalResult, NaiveDateTime, Timelike}; /// This macro calls a Windows API FFI and checks whether the function errored with the provided error_id. If an error returns, /// the macro will return an `Error::last_os_error()`. /// /// # Safety /// /// The provided error ID must align with the provided Windows API, providing the wrong ID could lead to UB. macro_rules! windows_sys_call { ($name:ident($($arg:expr),*), $error_id:expr) => { if $name($($arg),*) == $error_id { return Err(Error::last_os_error()); } } } const HECTONANOSECS_IN_SEC: i64 = 10_000_000; const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { offset(utc, false) } pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { offset(local, true) } /// Converts a local `NaiveDateTime` to the `time::Timespec`. pub(super) fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { let naive_sys_time = system_time_from_naive_date_time(d); let local_sys_time = match local { false => from_utc_time(naive_sys_time), true => from_local_time(naive_sys_time), }; if let Ok(offset) = local_sys_time { return LocalResult::Single(offset); } LocalResult::None } fn from_utc_time(utc_time: SYSTEMTIME) -> Result { let local_time = utc_to_local_time(&utc_time)?; let utc_secs = system_time_as_unix_seconds(&utc_time)?; let local_secs = system_time_as_unix_seconds(&local_time)?; let offset = (local_secs - utc_secs) as i32; Ok(FixedOffset::east_opt(offset).unwrap()) } fn from_local_time(local_time: SYSTEMTIME) -> Result { let utc_time = local_to_utc_time(&local_time)?; let utc_secs = system_time_as_unix_seconds(&utc_time)?; let local_secs = system_time_as_unix_seconds(&local_time)?; let offset = (local_secs - utc_secs) as i32; Ok(FixedOffset::east_opt(offset).unwrap()) } 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, } } pub(crate) fn local_to_utc_time(local: &SYSTEMTIME) -> Result { let mut sys_time = MaybeUninit::::uninit(); unsafe { windows_sys_call!( TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()), 0 ) }; // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can // assume the value is initialized. Ok(unsafe { sys_time.assume_init() }) } pub(crate) fn utc_to_local_time(utc_time: &SYSTEMTIME) -> Result { let mut local = MaybeUninit::::uninit(); unsafe { windows_sys_call!( SystemTimeToTzSpecificLocalTime(ptr::null(), utc_time, local.as_mut_ptr()), 0 ) }; // SAFETY: SystemTimeToTzSpecificLocalTime must have succeeded at this point, so we can // assume the value is initialized. Ok(unsafe { local.assume_init() }) } /// Returns a i64 value representing the unix seconds conversion of the current `WinSystemTime`. pub(crate) fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> Result { let mut init = MaybeUninit::::uninit(); unsafe { windows_sys_call!(SystemTimeToFileTime(st, init.as_mut_ptr()), 0) } // SystemTimeToFileTime must have succeeded at this point, so we can assum the value is // initalized. let filetime = unsafe { init.assume_init() }; let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); let unix_secs = (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC; Ok(unix_secs) } chrono-0.4.31/src/offset/mod.rs000064400000000000000000000570060072674642500144430ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! The time zone, which calculates offsets from the local time to UTC. //! //! There are four operations provided by the `TimeZone` trait: //! //! 1. Converting the local `NaiveDateTime` to `DateTime` //! 2. Converting the UTC `NaiveDateTime` to `DateTime` //! 3. Converting `DateTime` to the local `NaiveDateTime` //! 4. Constructing `DateTime` objects from various offsets //! //! 1 is used for constructors. 2 is used for the `with_timezone` method of date and time types. //! 3 is used for other methods, e.g. `year()` or `format()`, and provided by an associated type //! which implements `Offset` (which then passed to `TimeZone` for actual implementations). //! Technically speaking `TimeZone` has a total knowledge about given timescale, //! but `Offset` is used as a cache to avoid the repeated conversion //! and provides implementations for 1 and 3. //! An `TimeZone` instance can be reconstructed from the corresponding `Offset` instance. use core::fmt; use crate::format::{parse, ParseResult, Parsed, StrftimeItems}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; #[allow(deprecated)] use crate::{Date, DateTime}; mod fixed; pub use self::fixed::FixedOffset; #[cfg(feature = "clock")] mod local; #[cfg(feature = "clock")] pub use self::local::Local; 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)] pub enum LocalResult { /// Given local time representation is invalid. /// This can occur when, for example, the positive timezone transition. None, /// Given local time representation has a single unique result. Single(T), /// Given local time representation has multiple results and thus ambiguous. /// This can occur when, for example, the negative timezone transition. Ambiguous(T /*min*/, T /*max*/), } impl LocalResult { /// Returns `Some` only when the conversion result is unique, or `None` otherwise. #[must_use] pub fn single(self) -> Option { match self { LocalResult::Single(t) => Some(t), _ => None, } } /// Returns `Some` for the earliest possible conversion result, or `None` if none. #[must_use] pub fn earliest(self) -> Option { match self { LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), _ => None, } } /// Returns `Some` for the latest possible conversion result, or `None` if none. #[must_use] pub fn latest(self) -> Option { match self { LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), _ => None, } } /// Maps a `LocalResult` into `LocalResult` with given function. #[must_use] pub fn map U>(self, mut f: F) -> LocalResult { match self { LocalResult::None => LocalResult::None, LocalResult::Single(v) => LocalResult::Single(f(v)), LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), } } } #[allow(deprecated)] impl LocalResult> { /// 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> { match self { LocalResult::Single(d) => { d.and_time(time).map_or(LocalResult::None, LocalResult::Single) } _ => LocalResult::None, } } /// Makes a new `DateTime` from the current date, hour, minute and second. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { match self { LocalResult::Single(d) => { d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single) } _ => LocalResult::None, } } /// Makes a new `DateTime` from the current date, hour, minute, second and millisecond. /// The millisecond part can exceed 1,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] pub fn and_hms_milli_opt( self, hour: u32, min: u32, sec: u32, milli: u32, ) -> LocalResult> { match self { LocalResult::Single(d) => d .and_hms_milli_opt(hour, min, sec, milli) .map_or(LocalResult::None, LocalResult::Single), _ => LocalResult::None, } } /// Makes a new `DateTime` from the current date, hour, minute, second and microsecond. /// The microsecond part can exceed 1,000,000 in order to represent the leap second. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] pub fn and_hms_micro_opt( self, hour: u32, min: u32, sec: u32, micro: u32, ) -> LocalResult> { match self { LocalResult::Single(d) => d .and_hms_micro_opt(hour, min, sec, micro) .map_or(LocalResult::None, LocalResult::Single), _ => LocalResult::None, } } /// Makes a new `DateTime` 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. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] pub fn and_hms_nano_opt( self, hour: u32, min: u32, sec: u32, nano: u32, ) -> LocalResult> { match self { LocalResult::Single(d) => d .and_hms_nano_opt(hour, min, sec, nano) .map_or(LocalResult::None, LocalResult::Single), _ => LocalResult::None, } } } impl LocalResult { /// 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"), LocalResult::Single(t) => t, LocalResult::Ambiguous(t1, t2) => { panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) } } } } /// The offset from the local time to UTC. pub trait Offset: Sized + Clone + fmt::Debug { /// Returns the fixed offset from UTC to the local time stored. fn fix(&self) -> FixedOffset; } /// The time zone. /// /// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and /// [`DateTime`](../struct.DateTime.html) types. 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> { 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. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date, invalid month and/or day. #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] #[allow(deprecated)] fn ymd(&self, year: i32, month: u32, day: u32) -> Date { self.ymd_opt(year, month, day).unwrap() } /// 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. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// 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. #[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> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), None => LocalResult::None, } } /// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics 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(&self, year: i32, ordinal: u32) -> Date { self.yo_opt(year, ordinal).unwrap() } /// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// 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> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), None => LocalResult::None, } } /// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and /// the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// The resulting `Date` may have a different year from the input year. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics 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(&self, year: i32, week: u32, weekday: Weekday) -> Date { self.isoywd_opt(year, week, weekday).unwrap() } /// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and /// the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// The resulting `Date` may have a different year from the input year. /// /// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24), /// 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> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), None => LocalResult::None, } } /// Makes a new `DateTime` from the number of non-leap seconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// /// 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 on the out-of-range number of seconds and/or invalid nanosecond, /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime { self.timestamp_opt(secs, nsecs).unwrap() } /// Makes a new `DateTime` from the number of non-leap seconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// /// 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 `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> { match NaiveDateTime::from_timestamp_opt(secs, nsecs) { Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), None => LocalResult::None, } } /// Makes a new `DateTime` from the number of non-leap milliseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// Panics on out-of-range number of milliseconds for a non-panicking /// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt). #[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")] fn timestamp_millis(&self, millis: i64) -> DateTime { self.timestamp_millis_opt(millis).unwrap() } /// Makes a new `DateTime` from the number of non-leap milliseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// /// Returns `LocalResult::None` on out-of-range number of milliseconds /// and/or invalid nanosecond, otherwise always returns /// `LocalResult::Single`. /// /// # 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> { match NaiveDateTime::from_timestamp_millis(millis) { Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), None => LocalResult::None, } } /// 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. /// /// # Example /// /// ``` /// use chrono::{Utc, TimeZone}; /// /// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648); /// ``` fn timestamp_nanos(&self, nanos: i64) -> DateTime { let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000); if nanos < 0 { secs -= 1; nanos += 1_000_000_000; } self.timestamp_opt(secs, nanos as u32).unwrap() } /// 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> { 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. /// /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. #[deprecated(since = "0.4.29", note = "use `DateTime::parse_from_str` instead")] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_datetime_with_timezone(self) } /// Reconstructs the time zone from the offset. fn from_offset(offset: &Self::Offset) -> Self; /// Creates the offset(s) for given local `NaiveDate` if possible. fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult; /// Creates the offset(s) for given local `NaiveDateTime` if possible. fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; /// 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> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected Date::from_utc(*local, offset) }) } /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { self.offset_from_local_datetime(local) .map(|offset| DateTime::from_naive_utc_and_offset(*local - offset.fix(), offset)) } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset; /// Creates the offset for given UTC `NaiveDateTime`. This cannot fail. fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset; /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). #[allow(clippy::wrong_self_convention)] #[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")] #[allow(deprecated)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { Date::from_utc(*utc, self.offset_from_utc_date(utc)) } /// Converts the UTC `NaiveDateTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). #[allow(clippy::wrong_self_convention)] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_negative_millis() { 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_opt(-7000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC"); 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_opt(-7003).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC"); 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_opt(-1).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC"); 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_opt(-3600000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); for (millis, expected) in &[ (-7000, "1969-12-31 23:59:53 UTC"), (-7001, "1969-12-31 23:59:52.999 UTC"), (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis_opt(*millis) { LocalResult::Single(dt) => { assert_eq!(dt.to_string(), *expected); } e => panic!("Got {:?} instead of an okay answer", e), } } } #[test] fn test_negative_nanos() { let dt = Utc.timestamp_nanos(-1_000_000_000); assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); let dt = Utc.timestamp_nanos(-999_999_999); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000000001 UTC"); let dt = Utc.timestamp_nanos(-1); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999999 UTC"); let dt = Utc.timestamp_nanos(-60_000_000_000); assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); let dt = Utc.timestamp_nanos(-3_600_000_000_000); assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); } #[test] fn test_nanos_never_panics() { Utc.timestamp_nanos(i64::max_value()); 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"); } } chrono-0.4.31/src/offset/utc.rs000064400000000000000000000103320072674642500144460ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. //! The UTC (Coordinated Universal Time) time zone. use core::fmt; #[cfg(all( feature = "clock", not(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )) ))] use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[cfg(feature = "clock")] #[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). /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on the UTC struct is the preferred way to construct `DateTime` /// instances. /// /// # Example /// /// ``` /// use chrono::{TimeZone, NaiveDateTime, Utc}; /// /// let dt = Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_opt(61, 0).unwrap()); /// /// 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(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Utc { /// Returns a `Date` which corresponds to the current date. #[deprecated( since = "0.4.23", note = "use `Utc::now()` instead, potentially with `.date_naive()`" )] #[allow(deprecated)] #[must_use] pub fn today() -> Date { Utc::now().date() } /// Returns a `DateTime` which corresponds to the current date and time in UTC. /// /// See also the similar [`Local::now()`] which returns `DateTime`, i.e. the local date /// and time including offset from UTC. /// /// [`Local::now()`]: crate::Local::now /// /// # Example /// /// ``` /// # #![allow(unused_variables)] /// # use chrono::{FixedOffset, Utc}; /// // Current time in UTC /// let now_utc = Utc::now(); /// /// // Current date in UTC /// let today_utc = now_utc.date_naive(); /// /// // Current time in some timezone (let's use +05:00) /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); /// let now_with_offset = Utc::now().with_timezone(&offset); /// ``` #[cfg(not(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] #[must_use] pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); let naive = NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap(); Utc.from_utc_datetime(&naive) } /// 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 { let now = js_sys::Date::new_0(); DateTime::::from(now) } } impl TimeZone for Utc { type Offset = Utc; fn from_offset(_state: &Utc) -> Utc { Utc } fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { LocalResult::Single(Utc) } fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { LocalResult::Single(Utc) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc { Utc } fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Utc { Utc } } impl Offset for Utc { fn fix(&self) -> FixedOffset { FixedOffset::east_opt(0).unwrap() } } impl fmt::Debug for Utc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Z") } } impl fmt::Display for Utc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "UTC") } } chrono-0.4.31/src/round.rs000064400000000000000000000640540072674642500135260ustar 00000000000000// This is a part of Chrono. // See README.md and LICENSE.txt for details. use crate::datetime::DateTime; use crate::duration::Duration; use crate::NaiveDateTime; use crate::TimeZone; use crate::Timelike; use core::cmp::Ordering; use core::fmt; use core::marker::Sized; use core::ops::{Add, Sub}; /// Extension trait for subsecond rounding or truncation to a maximum number /// of digits. Rounding can be used to decrease the error variance when /// serializing/persisting to lower precision. Truncation is the default /// behavior in Chrono display formatting. Either can be used to guarantee /// equality (e.g. for testing) when round-tripping through a lower precision /// format. pub trait SubsecRound { /// Return a copy rounded to the specified number of subsecond digits. With /// 9 or more digits, self is returned unmodified. Halfway values are /// rounded up (away from zero). /// /// # Example /// ``` rust /// # 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); /// ``` fn round_subsecs(self, digits: u16) -> Self; /// Return a copy truncated to the specified number of subsecond /// digits. With 9 or more digits, self is returned unmodified. /// /// # Example /// ``` rust /// # 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); /// ``` fn trunc_subsecs(self, digits: u16) -> Self; } impl SubsecRound for T where T: Timelike + Add + Sub, { fn round_subsecs(self, digits: u16) -> T { let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { let delta_up = span - delta_down; if delta_up <= delta_down { self + Duration::nanoseconds(delta_up.into()) } else { self - Duration::nanoseconds(delta_down.into()) } } else { self // unchanged } } fn trunc_subsecs(self, digits: u16) -> T { let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { self - Duration::nanoseconds(delta_down.into()) } else { self // unchanged } } } // Return the maximum span in nanoseconds for the target number of digits. const fn span_for_digits(digits: u16) -> u32 { // fast lookup form of: 10^(9-min(9,digits)) match digits { 0 => 1_000_000_000, 1 => 100_000_000, 2 => 10_000_000, 3 => 1_000_000, 4 => 100_000, 5 => 10_000, 6 => 1_000, 7 => 100, 8 => 10, _ => 1, } } /// Extension trait for rounding or truncating a DateTime by a Duration. /// /// # 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. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating #[cfg(feature = "std")] type Err: std::error::Error; /// Error that can occur in rounding or truncating #[cfg(not(feature = "std"))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by Duration. /// /// # Example /// ``` rust /// # use chrono::{DurationRound, Duration, 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(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( /// dt.duration_round(Duration::days(1)).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` fn duration_round(self, duration: Duration) -> Result; /// Return a copy truncated by Duration. /// /// # Example /// ``` rust /// # use chrono::{DurationRound, Duration, 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(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` fn duration_trunc(self, duration: Duration) -> Result; } impl DurationRound for DateTime { type Err = RoundingError; fn duration_round(self, duration: Duration) -> Result { duration_round(self.naive_local(), self, duration) } fn duration_trunc(self, duration: Duration) -> Result { duration_trunc(self.naive_local(), self, duration) } } impl DurationRound for NaiveDateTime { type Err = RoundingError; fn duration_round(self, duration: Duration) -> Result { duration_round(self, self, duration) } fn duration_trunc(self, duration: Duration) -> Result { duration_trunc(self, self, duration) } } fn duration_round( naive: NaiveDateTime, original: T, duration: Duration, ) -> Result where T: Timelike + Add + Sub, { 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 > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } 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 + Duration::nanoseconds(delta_up)) } else { Ok(original - Duration::nanoseconds(delta_down)) } } } else { Err(RoundingError::DurationExceedsLimit) } } fn duration_trunc( naive: NaiveDateTime, original: T, duration: Duration, ) -> Result where T: Timelike + Add + Sub, { 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 > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), Ordering::Greater => Ok(original - Duration::nanoseconds(delta_down)), Ordering::Less => Ok(original - Duration::nanoseconds(span - delta_down.abs())), } } else { Err(RoundingError::DurationExceedsLimit) } } /// An error from rounding by `Duration` /// /// See: [`DurationRound`] #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum RoundingError { /// Error when the Duration exceeds the Duration from or until the Unix epoch. /// /// ``` rust /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!( /// dt.duration_round(Duration::days(365)), /// Err(RoundingError::DurationExceedsTimestamp), /// ); /// ``` DurationExceedsTimestamp, /// Error when `Duration.num_nanoseconds` exceeds the limit. /// /// ``` rust /// # use chrono::{DurationRound, Duration, 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)), /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` DurationExceedsLimit, /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust /// # use chrono::{DurationRound, Duration, 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),); /// ``` TimestampExceedsLimit, } impl fmt::Display for RoundingError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { RoundingError::DurationExceedsTimestamp => { write!(f, "duration in nanoseconds exceeds timestamp") } RoundingError::DurationExceedsLimit => { write!(f, "duration exceeds num_nanoseconds limit") } RoundingError::TimestampExceedsLimit => { write!(f, "timestamp exceeds num_nanoseconds limit") } } } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] fn description(&self) -> &str { "error from rounding or truncating with DurationRound" } } #[cfg(test)] mod tests { use super::{Duration, DurationRound, RoundingError, SubsecRound}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Timelike; use crate::{NaiveDate, NaiveDateTime}; #[test] fn test_round_subsecs() { 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(), 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 .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); assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000); assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000); assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 28); } #[test] fn test_round_leap_nanos() { 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); assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000); assert_eq!(dt.round_subsecs(1).second(), 59); assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 0); } #[test] fn test_trunc_subsecs() { 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(), 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 .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); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000); assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000); assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).second(), 27); } #[test] fn test_trunc_leap_nanos() { 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); assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000); assert_eq!(dt.trunc_subsecs(1).second(), 59); assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000); assert_eq!(dt.trunc_subsecs(0).second(), 59); } #[test] fn test_duration_round() { 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(Duration::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500 UTC" ); assert_eq!( dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); // 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(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // 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(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( dt.duration_round(Duration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( dt.duration_round(Duration::minutes(30)).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( dt.duration_round(Duration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( dt.duration_round(Duration::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(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" ); assert_eq!( dt.duration_round(Duration::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(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" ); assert_eq!( dt.duration_round(Duration::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(Duration::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500" ); assert_eq!( dt.duration_round(Duration::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(Duration::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(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( dt.duration_round(Duration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( dt.duration_round(Duration::minutes(30)).unwrap().to_string(), "2012-12-12 18:30:00" ); assert_eq!( dt.duration_round(Duration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( dt.duration_round(Duration::days(1)).unwrap().to_string(), "2012-12-13 00:00:00" ); } #[test] fn test_duration_round_pre_epoch() { 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(), "1969-12-12 12:10:00 UTC" ); } #[test] fn test_duration_trunc() { 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(), "2016-12-31 23:59:59.170 UTC" ); // 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(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // 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(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( dt.duration_trunc(Duration::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(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" ); assert_eq!( dt.duration_trunc(Duration::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(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" ); assert_eq!( dt.duration_trunc(Duration::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(Duration::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(Duration::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(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( dt.duration_trunc(Duration::days(1)).unwrap().to_string(), "2012-12-12 00:00:00" ); } #[test] fn test_duration_trunc_pre_epoch() { 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(), "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 = Duration::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 = Duration::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 = Duration::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } } chrono-0.4.31/src/traits.rs000064400000000000000000000354440072674642500137060ustar 00000000000000use 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`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// /// [`NaiveDate`]: crate::NaiveDate /// [`DateTime`]: crate::DateTime /// /// # Examples /// /// ``` /// use chrono::{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; /// Makes a new value with the month number (starting from 1) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `month` is out of range. /// /// [`DateTime`]: crate::DateTime /// /// # Examples /// /// ``` /// use chrono::{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 { /// date.with_year(year)?.with_month(month) /// } /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value /// /// // Correct version: /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { /// NaiveDate::from_ymd_opt(year, month, date.day()) /// } /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); /// ``` fn with_month(&self, month: u32) -> Option; /// Makes a new value with the month number (starting from 0) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `month0` is out of range. /// /// [`DateTime`]: crate::DateTime fn with_month0(&self, month0: u32) -> Option; /// Makes a new value with the day of month (starting from 1) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (for example `day(31)` in April). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `day` is out of range. /// /// [`DateTime`]: crate::DateTime fn with_day(&self, day: u32) -> Option; /// Makes a new value with the day of month (starting from 0) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (for example `day0(30)` in April). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `day0` is out of range. /// /// [`DateTime`]: crate::DateTime fn with_day0(&self, day0: u32) -> Option; /// Makes a new value with the day of year (starting from 1) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `ordinal` is out of range. /// /// [`DateTime`]: crate::DateTime fn with_ordinal(&self, ordinal: u32) -> Option; /// Makes a new value with the day of year (starting from 0) changed. /// /// # Errors /// /// Returns `None` when: /// /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). /// - In case of [`DateTime`] if the resulting date and time fall within a timezone /// transition such as from DST to standard time. /// - The value for `ordinal0` is out of range. /// /// [`DateTime`]: crate::DateTime fn with_ordinal0(&self, ordinal0: u32) -> Option; /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. /// /// # Examples /// /// ``` /// use chrono::{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; /// Makes a new value with the minute number changed. /// /// Returns `None` when the resulting value would be invalid. fn with_minute(&self, min: u32) -> Option; /// Makes a new value with the second number changed. /// /// Returns `None` when the resulting value would be invalid. /// As with the [`second`](#tymethod.second) method, /// the input range is restricted to 0 through 59. fn with_second(&self, sec: u32) -> Option; /// Makes a new value with nanoseconds since the whole non-leap second changed. /// /// Returns `None` when the resulting value would be invalid. /// As with the [`nanosecond`](#tymethod.nanosecond) method, /// the input range can exceed 1,000,000,000 for leap seconds. fn with_nanosecond(&self, nano: u32) -> Option; /// Returns the number of non-leap seconds past the last midnight. /// /// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399. /// /// This method is not intended to provide the real number of seconds since midnight on a given /// day. It does not take things like DST transitions into account. #[inline] fn num_seconds_from_midnight(&self) -> u32 { self.hour() * 3600 + self.minute() * 60 + self.second() } } #[cfg(test)] mod tests { use super::Datelike; use crate::{Duration, NaiveDate}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. /// /// The alternative implementation is not as short as the current one but it is simpler to /// understand, with less unexplained magic constants. #[test] fn test_num_days_from_ce_against_alternative_impl() { /// Returns the number of multiples of `div` in the range `start..end`. /// /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the /// behaviour is defined by the following equation: /// `in_between(start, end, div) == - in_between(end, start, div)`. /// /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. /// /// # Panics /// /// Panics if `div` is not positive. fn in_between(start: i32, end: i32, div: i32) -> i32 { assert!(div > 0, "in_between: nonpositive div = {}", div); let start = (start.div_euclid(div), start.rem_euclid(div)); let end = (end.div_euclid(div), end.rem_euclid(div)); // The lowest multiple of `div` greater than or equal to `start`, divided. let start = start.0 + (start.1 != 0) as i32; // The lowest multiple of `div` greater than or equal to `end`, divided. let end = end.0 + (end.1 != 0) as i32; end - start } /// Alternative implementation to `Datelike::num_days_from_ce` fn num_days_from_ce(date: &Date) -> i32 { let year = date.year(); let diff = move |div| in_between(1, year, div); // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all // the multiples of 4 except multiples of 100 but including multiples of 400. date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) } for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); assert_eq!( jan1_year.num_days_from_ce(), num_days_from_ce(&jan1_year), "on {:?}", jan1_year ); let mid_year = jan1_year + Duration::days(133); assert_eq!( mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {:?}", mid_year ); } } } chrono-0.4.31/src/weekday.rs000064400000000000000000000312520072674642500140220ustar 00000000000000use core::fmt; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use crate::OutOfRange; /// The day of week. /// /// The order of the days of week depends on the context. /// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) /// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. /// /// # Example /// ``` /// use chrono::Weekday; /// /// let monday = "Monday".parse::().unwrap(); /// assert_eq!(monday, Weekday::Mon); /// /// let sunday = Weekday::try_from(6).unwrap(); /// assert_eq!(sunday, Weekday::Sun); /// /// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 /// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1 /// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0 /// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1 /// /// assert_eq!(sunday.succ(), monday); /// assert_eq!(sunday.pred(), Weekday::Sat); /// ``` #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", 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 for Weekday { type Error = OutOfRange; fn try_from(value: u8) -> Result { match value { 0 => Ok(Weekday::Mon), 1 => Ok(Weekday::Tue), 2 => Ok(Weekday::Wed), 3 => Ok(Weekday::Thu), 4 => Ok(Weekday::Fri), 5 => Ok(Weekday::Sat), 6 => Ok(Weekday::Sun), _ => Err(OutOfRange::new()), } } } /// Any weekday can be represented as an integer from 0 to 6, which equals to /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. /// Do not heavily depend on this though; use explicit methods whenever possible. impl num_traits::FromPrimitive for Weekday { #[inline] fn from_i64(n: i64) -> Option { match n { 0 => Some(Weekday::Mon), 1 => Some(Weekday::Tue), 2 => Some(Weekday::Wed), 3 => Some(Weekday::Thu), 4 => Some(Weekday::Fri), 5 => Some(Weekday::Sat), 6 => Some(Weekday::Sun), _ => None, } } #[inline] fn from_u64(n: u64) -> Option { match n { 0 => Some(Weekday::Mon), 1 => Some(Weekday::Tue), 2 => Some(Weekday::Wed), 3 => Some(Weekday::Thu), 4 => Some(Weekday::Fri), 5 => Some(Weekday::Sat), 6 => Some(Weekday::Sun), _ => None, } } } /// An error resulting from reading `Weekday` value with `FromStr`. #[derive(Clone, PartialEq, Eq)] pub struct ParseWeekdayError { pub(crate) _dummy: (), } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(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")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod weekday_serde { use super::Weekday; use core::fmt; use serde::{de, ser}; impl ser::Serialize for Weekday { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.collect_str(&self) } } struct WeekdayVisitor; impl<'de> de::Visitor<'de> for WeekdayVisitor { type Value = Weekday; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Weekday") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value.parse().map_err(|_| E::custom("short or long weekday names expected")) } } impl<'de> de::Deserialize<'de> for Weekday { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(WeekdayVisitor) } } } #[cfg(test)] 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::(str).unwrap(); assert_eq!(weekday, expected_weekday); } let errors: Vec<&str> = vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; for str in errors { from_str::(str).unwrap_err(); } } } chrono-0.4.31/taplo.toml000064400000000000000000000001230072674642500132410ustar 00000000000000include = ["deny.toml", "**/Cargo.toml"] [formatting] inline_table_expand = false chrono-0.4.31/tests/dateutils.rs000064400000000000000000000137260072674642500147500ustar 00000000000000#![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::Duration::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") .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::Duration::days(55); } } chrono-0.4.31/tests/wasm.rs000064400000000000000000000053530072674642500137160ustar 00000000000000//! 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. #![cfg(all( target_arch = "wasm32", feature = "wasmbind", feature = "clock", not(any(target_os = "emscripten", target_os = "wasi")) ))] use chrono::prelude::*; use wasm_bindgen_test::*; #[wasm_bindgen_test] fn now() { let utc: DateTime = Utc::now(); let local: DateTime = Local::now(); // 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, ); let tz = env!("TZ"); eprintln!("testing with tz={}", tz); // Ensure offset retrieved when getting local time is correct let expected_offset = match tz { "ACST-9:30" => FixedOffset::east_opt(19 * 30 * 60).unwrap(), "Asia/Katmandu" => FixedOffset::east_opt(23 * 15 * 60).unwrap(), // No DST thankfully "EDT" | "EST4" | "-0400" => FixedOffset::east_opt(-4 * 60 * 60).unwrap(), "EST" | "-0500" => FixedOffset::east_opt(-5 * 60 * 60).unwrap(), "UTC0" | "+0000" => FixedOffset::east_opt(0).unwrap(), tz => panic!("unexpected TZ {}", tz), }; assert_eq!( &expected_offset, local.offset(), "expected: {:?} local: {:?}", expected_offset, local.offset(), ); } #[wasm_bindgen_test] fn from_is_exact() { let now = js_sys::Date::new_0(); let dt = DateTime::::from(now.clone()); assert_eq!(now.get_time() as i64, dt.timestamp_millis()); } #[wasm_bindgen_test] fn local_from_local_datetime() { let now = Local::now(); let ndt = now.naive_local(); let res = match Local.from_local_datetime(&ndt).single() { Some(v) => v, None => panic! {"Required for test!"}, }; assert_eq!(now, res); } #[wasm_bindgen_test] fn convert_all_parts_with_milliseconds() { let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); let js_date = js_sys::Date::from(time); assert_eq!(js_date.get_utc_full_year(), 2020); assert_eq!(js_date.get_utc_month(), 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); } chrono-0.4.31/tests/win_bindings.rs000064400000000000000000000013520072674642500154140ustar 00000000000000#![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); } }