feat: patch chrono

This commit is contained in:
2023-01-20 23:25:18 +08:00
parent cf8e579f27
commit c997aab841
71 changed files with 27208 additions and 178 deletions

View File

@@ -10,9 +10,11 @@ crate-type = ['cdylib']
[dependencies] [dependencies]
boa_engine = { version = "0.16.0", path = "external/boa/boa_engine"} boa_engine = { version = "0.16.0", path = "external/boa/boa_engine"}
getrandom = { version = "0.2.8", features = ["js"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
[patch.crates-io] [patch.crates-io]
iana-time-zone = { path = "external/iana-time-zone" } iana-time-zone = { path = "external/iana-time-zone" }
getrandom = { path = "external/getrandom" } getrandom = { path = "external/getrandom" }
chrono = { path = "external/chrono" }

View File

@@ -0,0 +1 @@
febb8dc168325ac471b54591c925c48b6a485962 # cargo fmt

View File

@@ -0,0 +1,3 @@
### Thanks for contributing to chrono!
Please consider adding a test to ensure your bug fix/feature will not break in the future.

View File

@@ -0,0 +1,31 @@
name: lint
on:
push:
branches: [main, 0.4.x]
pull_request:
paths:
- "**.rs"
- .github/**
- .ci/**
- Cargo.toml
- deny.toml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo fmt -- --check --color=always
- run: cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive
env:
RUSTFLAGS: "-Dwarnings"
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1

View File

@@ -0,0 +1,155 @@
name: All Tests and Builds
on:
push:
branches: [main, 0.4.x]
pull_request:
paths:
- "**.rs"
- .github/**
- .ci/**
- Cargo.toml
jobs:
timezones_linux:
strategy:
matrix:
os: [ubuntu-latest]
tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo test --all-features --color=always -- --color=always
timezones_other:
strategy:
matrix:
os: [macos-latest, windows-latest]
tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo test --lib --all-features --color=always -- --color=always
- run: cargo test --doc --all-features --color=always -- --color=always
# later this may be able to be included with the below
# kept seperate for now as the following don't compile on 1.38.0
# * rkyv
# * criterion
rust_msrv:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.38.0
- uses: Swatinem/rust-cache@v1
# 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,serde,winapi --color=always -- --color=always
- run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always
rust_versions:
strategy:
matrix:
os: [ubuntu-latest]
rust_version: ["stable", "beta", "nightly"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust_version }}
- uses: Swatinem/rust-cache@v1
# 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@v2
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v1
- run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-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@v2
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- run: cargo build --target ${{ matrix.target }} --color=always
working-directory: ./ci/core-test
alternative_targets:
strategy:
matrix:
os: [ubuntu-latest]
target: [wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-node@v1
with:
node-version: "12"
- run: |
export RUST_BACKTRACE=1
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
wasm-pack --version
- run: cargo build --target ${{ matrix.target }} --color=always
features_check_wasm:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v1
- run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales
cross-targets:
strategy:
matrix:
target:
- x86_64-sun-solaris
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: cargo install cross
- uses: Swatinem/rust-cache@v1
- run: cross check --target ${{ matrix.target }}
check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@nightly
- run: cargo +nightly doc --all-features --no-deps
env:
RUSTDOCFLAGS: "-D warnings --cfg docsrs"

View File

@@ -0,0 +1,6 @@
target
Cargo.lock
.tool-versions
# for jetbrains users
.idea/

View File

@@ -0,0 +1,43 @@
Chrono is mainly written by Kang Seonghoon <public+rust@mearie.org>,
and also the following people (in ascending order):
Alex Mikhalev <alexmikhalevalex@gmail.com>
Alexander Bulaev <alexbool@yandex-team.ru>
Ashley Mannix <ashleymannix@live.com.au>
Ben Boeckel <mathstuf@gmail.com>
Ben Eills <ben@beneills.com>
Brandon W Maister <bwm@knewton.com>
Brandon W Maister <quodlibetor@gmail.com>
Cecile Tonglet <cecile.tonglet@cecton.com>
Colin Ray <r.colinray@gmail.com>
Corey Farwell <coreyf@rwell.org>
Dan <dan@ebip.co.uk>
Danilo Bargen <mail@dbrgn.ch>
David Hewson <dev@daveid.co.uk>
David Ross <daboross@daboross.net>
David Tolnay <dtolnay@gmail.com>
David Willie <david.willie.1@gmail.com>
Eric Findlay <e.findlay@protonmail.ch>
Eunchong Yu <kroisse@gmail.com>
Frans Skarman <frans.skarman@gmail.com>
Huon Wilson <dbau.pp+github@gmail.com>
Igor Gnatenko <ignatenko@redhat.com>
Jake Vossen <jake@vossen.dev>
Jim Turner <jturner314@gmail.com>
Jisoo Park <xxxyel@gmail.com>
Joe Wilm <joe@jwilm.com>
John Heitmann <jheitmann@gmail.com>
John Nagle <nagle@sitetruth.com>
Jonas mg <jonasmg@yepmail.net>
János Illés <ijanos@gmail.com>
Ken Tossell <ken@tossell.net>
Martin Risell Lilja <martin.risell.lilja@gmail.com>
Richard Petrie <rap1011@ksu.edu>
Ryan Lewis <ryansname@gmail.com>
Sergey V. Shadoy <shadoysv@yandex.ru>
Sergey V. Galtsev <sergey.v.galtsev@github.com>
Steve Klabnik <steve@steveklabnik.com>
Tom Gallacher <tomgallacher23@gmail.com>
klutzy <klutzytheklutzy@gmail.com>
kud1ing <github@kudling.de>
Yohan Boogaert <yozhgoor@outlook.com>

View File

@@ -0,0 +1,731 @@
ChangeLog 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<Utc>`.
## 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<Tz>`, meaning that e.g.
`dt.to_string().parse::<DateTime<Utc>>()` 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<Item>`, so one can
use values or references. (@michalsrb #358)
* Add built-in support for structs with nested `Option<Datetime>` 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<Tz>` 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<Utc>` and `DateTime<Local>` 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<Tz>` 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<Tz>` and fixed unwanted conversion problem
that only occurs with positive UTC offsets. (#27)
## 0.2.3 (2015-02-27)
### Added
- `DateTime<Tz>` and `Date<Tz>` 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<FixedOffset>`.
- 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<Date<Off>>` methods
to make `LocalResult<DateTime<Off>>`.
### 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`.

View File

@@ -0,0 +1,76 @@
[package]
name = "chrono"
version = "0.4.23"
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/Apache-2.0"
exclude = ["/ci/*"]
edition = "2018"
[lib]
name = "chrono"
[features]
#default = ["clock", "std", "oldtime", "wasmbind"]
default = ["clock", "std", "oldtime"]
alloc = []
libc = []
std = []
clock = ["std", "winapi", "iana-time-zone"]
oldtime = ["time"]
#wasmbind = ["wasm-bindgen", "js-sys"]
unstable-locales = ["pure-rust-locales", "alloc"]
__internal_bench = ["criterion"]
__doctest = []
[dependencies]
time = { version = "0.1.43", optional = true }
num-integer = { version = "0.1.36", default-features = false }
num-traits = { version = "0.2", default-features = false }
rustc-serialize = { version = "0.3.20", optional = true }
serde = { version = "1.0.99", default-features = false, optional = true }
pure-rust-locales = { version = "0.5.2", optional = true }
criterion = { version = "0.4.0", optional = true }
rkyv = {version = "0.7", optional = true}
iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] }
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]
winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true }
[dev-dependencies]
serde_json = { version = "1" }
serde_derive = { version = "1", default-features = false }
bincode = { version = "1.3.0" }
num-iter = { version = "0.1.35", default-features = false }
doc-comment = { version = "0.3" }
[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"]
[[bench]]
name = "chrono"
required-features = ["__internal_bench"]
harness = false
[[bench]]
name = "serde"
required-features = ["__internal_bench", "serde"]
harness = false

View File

@@ -0,0 +1,240 @@
Rust-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]: <http://opensource.org/licenses/MIT>, 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]: <http://www.apache.org/licenses/LICENSE-2.0>, 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.
~~~~

View File

@@ -0,0 +1,26 @@
# this Makefile is mostly for the packaging convenience.
# casual users should use `cargo` to retrieve the appropriate version of Chrono.
CHANNEL=stable
.PHONY: all
all:
@echo 'Try `cargo build` instead.'
.PHONY: authors
authors:
echo 'Chrono is mainly written by Kang Seonghoon <public+rust@mearie.org>,' > AUTHORS.txt
echo 'and also the following people (in ascending order):' >> AUTHORS.txt
echo >> AUTHORS.txt
git log --format='%aN <%aE>' | grep -v 'Kang Seonghoon' | sort -u >> AUTHORS.txt
.PHONY: readme README.md
readme: README.md
.PHONY: test
test:
CHANNEL=$(CHANNEL) ./ci/travis.sh
.PHONY: doc
doc: authors readme
cargo doc --features 'serde rustc-serialize bincode'

View File

@@ -0,0 +1,59 @@
[Chrono][docsrs]: Date and Time for Rust
========================================
[![Chrono GitHub Actions][gh-image]][gh-checks]
[![Chrono on crates.io][cratesio-image]][cratesio]
[![Chrono on docs.rs][docsrs-image]][docsrs]
[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter]
[gh-image]: https://github.com/chronotope/chrono/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
[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg
[gitter]: https://gitter.im/chrono-rs/chrono
It aims to be a feature-complete superset of
the [time](https://github.com/rust-lang-deprecated/time) library.
In particular,
* Chrono strictly adheres to ISO 8601.
* Chrono is timezone-aware by default, with separate timezone-naive types.
* Chrono is space-optimal and (while not being the primary goal) reasonably efficient.
There were several previous attempts to bring a good date and time library to Rust,
which Chrono builds upon and should acknowledge:
* [Initial research on
the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md)
* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs)
* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime)
## Limitations
Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported.
Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others.
Date types are limited in about +/- 262,000 years from the common epoch.
Time types are limited in the nanosecond accuracy.
[Leap seconds are supported in the representation but
Chrono doesn't try to make use of them](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html#leap-second-handling).
(The main reason is that leap seconds are not really predictable.)
Almost *every* operation over the possible leap seconds will ignore them.
Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale
if you want.
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.

View File

@@ -0,0 +1,12 @@
# TODO: delete this without breaking all PRs
environment:
matrix:
- TARGET: nightly-i686-pc-windows-gnu
matrix:
allow_failures:
- channel: nightly
build: false
test_script:
- echo "stub"

View File

@@ -0,0 +1,137 @@
//! Benchmarks for chrono that just depend on std
#![cfg(feature = "__internal_bench")]
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use chrono::prelude::*;
use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags};
fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) {
c.bench_function("bench_datetime_parse_from_rfc2822", |b| {
b.iter(|| {
let str = black_box("Wed, 18 Feb 2015 23:16:09 +0000");
DateTime::parse_from_rfc2822(str).unwrap()
})
});
}
fn bench_datetime_parse_from_rfc3339(c: &mut Criterion) {
c.bench_function("bench_datetime_parse_from_rfc3339", |b| {
b.iter(|| {
let str = black_box("2015-02-18T23:59:60.234567+05:00");
DateTime::parse_from_rfc3339(str).unwrap()
})
});
}
fn bench_datetime_from_str(c: &mut Criterion) {
c.bench_function("bench_datetime_from_str", |b| {
b.iter(|| {
use std::str::FromStr;
let str = black_box("2019-03-30T18:46:57.193Z");
DateTime::<Utc>::from_str(str).unwrap()
})
});
}
fn bench_datetime_to_rfc2822(c: &mut Criterion) {
let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
let dt = pst
.from_local_datetime(
&NaiveDate::from_ymd_opt(2018, 1, 11)
.unwrap()
.and_hms_nano_opt(10, 5, 13, 84_660_000)
.unwrap(),
)
.unwrap();
c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822()));
}
fn bench_datetime_to_rfc3339(c: &mut Criterion) {
let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
let dt = pst
.from_local_datetime(
&NaiveDate::from_ymd_opt(2018, 1, 11)
.unwrap()
.and_hms_nano_opt(10, 5, 13, 84_660_000)
.unwrap(),
)
.unwrap();
c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339()));
}
fn bench_year_flags_from_year(c: &mut Criterion) {
c.bench_function("bench_year_flags_from_year", |b| {
b.iter(|| {
for year in -999i32..1000 {
__BenchYearFlags::from_year(year);
}
})
});
}
fn bench_get_local_time(c: &mut Criterion) {
c.bench_function("bench_get_local_time", |b| {
b.iter(|| {
let _ = Local::now();
})
});
}
/// Returns the number of multiples of `div` in the range `start..end`.
///
/// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the
/// behaviour is defined by the following equation:
/// `in_between(start, end, div) == - in_between(end, start, div)`.
///
/// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`.
///
/// # Panics
///
/// Panics if `div` is not positive.
fn in_between(start: i32, end: i32, div: i32) -> i32 {
assert!(div > 0, "in_between: nonpositive div = {}", div);
let start = (start.div_euclid(div), start.rem_euclid(div));
let end = (end.div_euclid(div), end.rem_euclid(div));
// The lowest multiple of `div` greater than or equal to `start`, divided.
let start = start.0 + (start.1 != 0) as i32;
// The lowest multiple of `div` greater than or equal to `end`, divided.
let end = end.0 + (end.1 != 0) as i32;
end - start
}
/// Alternative implementation to `Datelike::num_days_from_ce`
fn num_days_from_ce_alt<Date: Datelike>(date: &Date) -> i32 {
let year = date.year();
let diff = move |div| in_between(1, year, div);
// 365 days a year, one more in leap years. In the gregorian calendar, leap years are all
// the multiples of 4 except multiples of 100 but including multiples of 400.
date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400)
}
fn bench_num_days_from_ce(c: &mut Criterion) {
let mut group = c.benchmark_group("num_days_from_ce");
for year in &[1, 500, 2000, 2019] {
let d = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap();
group.bench_with_input(BenchmarkId::new("new", year), &d, |b, y| {
b.iter(|| num_days_from_ce_alt(y))
});
group.bench_with_input(BenchmarkId::new("classic", year), &d, |b, y| {
b.iter(|| y.num_days_from_ce())
});
}
}
criterion_group!(
benches,
bench_datetime_parse_from_rfc2822,
bench_datetime_parse_from_rfc3339,
bench_datetime_from_str,
bench_datetime_to_rfc2822,
bench_datetime_to_rfc3339,
bench_year_flags_from_year,
bench_num_days_from_ce,
bench_get_local_time,
);
criterion_main!(benches);

View File

@@ -0,0 +1,29 @@
#![cfg(feature = "__internal_bench")]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use chrono::NaiveDateTime;
fn bench_ser_naivedatetime_string(c: &mut Criterion) {
c.bench_function("bench_ser_naivedatetime_string", |b| {
let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap();
b.iter(|| {
black_box(serde_json::to_string(&dt)).unwrap();
});
});
}
fn bench_ser_naivedatetime_writer(c: &mut Criterion) {
c.bench_function("bench_ser_naivedatetime_writer", |b| {
let mut s: Vec<u8> = Vec::with_capacity(20);
let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap();
b.iter(|| {
let s = &mut s;
s.clear();
black_box(serde_json::to_writer(s, &dt)).unwrap();
});
});
}
criterion_group!(benches, bench_ser_naivedatetime_writer, bench_ser_naivedatetime_string);
criterion_main!(benches);

View File

@@ -0,0 +1,14 @@
[package]
name = "core-test"
version = "0.1.0"
authors = [
"Kang Seonghoon <public+rust@mearie.org>",
"Brandon W Maister <quodlibetor@gmail.com>",
]
edition = "2018"
[dependencies]
chrono = { path = "../..", default-features = false, features = ["serde"] }
[features]
alloc = ["chrono/alloc"]

View File

@@ -0,0 +1,7 @@
#![no_std]
use chrono::{TimeZone, Utc};
pub fn create_time() {
let _ = Utc.with_ymd_and_hms(2019, 1, 1, 0, 0, 0).unwrap();
}

View File

@@ -0,0 +1 @@
msrv = "1.38"

View File

@@ -0,0 +1,13 @@
[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"

View File

@@ -0,0 +1,4 @@
target
corpus
artifacts

View File

@@ -0,0 +1,24 @@
[package]
name = "chrono-fuzz"
version = "0.0.0"
authors = ["David Korczynski <david@adalogics.com>"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
[dependencies.chrono]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzz_reader"
path = "fuzz_targets/fuzz_reader.rs"

View File

@@ -0,0 +1,12 @@
# Fuzzing Chrono
To fuzz Chrono we rely on the [Cargo-fuzz](https://rust-fuzz.github.io/) project.
To install cargo-fuzz:
```
cargo install cargo-fuzz
```
To run the Chrono fuzzer, navigate to the top directory of chrono and issue the following command:
```
cargo-fuzz run fuzz_reader
```

View File

@@ -0,0 +1,10 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
use chrono::prelude::*;
if let Ok(data) = std::str::from_utf8(data) {
let _ = DateTime::parse_from_rfc2822(data);
let _ = DateTime::parse_from_rfc3339(data);
}
});

View File

@@ -0,0 +1 @@
use_small_heuristics = "Max"

View File

@@ -0,0 +1,645 @@
// 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", test))]
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};
#[cfg(feature = "unstable-locales")]
use crate::format::Locale;
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::format::{DelayedFormat, Item, StrftimeItems};
use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
use crate::offset::{TimeZone, Utc};
use crate::oldtime::Duration as OldDuration;
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<Tz>`:
///
/// - 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<Tz>` instead")]
#[derive(Clone)]
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
pub struct Date<Tz: TimeZone> {
date: NaiveDate,
offset: Tz::Offset,
}
/// The minimum possible `Date`.
#[allow(deprecated)]
#[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")]
pub const MIN_DATE: Date<Utc> = Date::<Utc>::MIN_UTC;
/// The maximum possible `Date`.
#[allow(deprecated)]
#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")]
pub const MAX_DATE: Date<Utc> = Date::<Utc>::MAX_UTC;
impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `Date` with given *UTC* date and offset.
/// The local date should be constructed via the `TimeZone` trait.
//
// note: this constructor is purposely not named to `new` to discourage the direct usage.
#[inline]
pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date<Tz> {
Date { date, offset }
}
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
/// The offset in the current date is preserved.
///
/// Panics on invalid datetime.
#[inline]
pub fn and_time(&self, time: NaiveTime) -> Option<DateTime<Tz>> {
let localdt = self.naive_local().and_time(time);
self.timezone().from_local_datetime(&localdt).single()
}
/// 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]
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime<Tz> {
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]
pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<DateTime<Tz>> {
NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time))
}
/// 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]
pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime<Tz> {
self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time")
}
/// 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]
pub fn and_hms_milli_opt(
&self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> Option<DateTime<Tz>> {
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]
pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime<Tz> {
self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time")
}
/// 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]
pub fn and_hms_micro_opt(
&self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> Option<DateTime<Tz>> {
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]
pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime<Tz> {
self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time")
}
/// 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]
pub fn and_hms_nano_opt(
&self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> Option<DateTime<Tz>> {
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]
pub fn succ(&self) -> Date<Tz> {
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]
pub fn succ_opt(&self) -> Option<Date<Tz>> {
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]
pub fn pred(&self) -> Date<Tz> {
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]
pub fn pred_opt(&self) -> Option<Date<Tz>> {
self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
/// Retrieves an associated offset from UTC.
#[inline]
pub fn offset(&self) -> &Tz::Offset {
&self.offset
}
/// Retrieves an associated time zone.
#[inline]
pub fn timezone(&self) -> Tz {
TimeZone::from_offset(&self.offset)
}
/// Changes the associated time zone.
/// This does not change the actual `Date` (but will change the string representation).
#[inline]
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> Date<Tz2> {
tz.from_utc_date(&self.date)
}
/// Adds given `Duration` to the current date.
///
/// Returns `None` when it will result in overflow.
#[inline]
pub fn checked_add_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
let date = 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]
pub fn checked_sub_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
let date = self.date.checked_sub_signed(rhs)?;
Some(Date { date, offset: self.offset })
}
/// Subtracts another `Date` from the current date.
/// Returns a `Duration` of integral numbers.
///
/// This does not overflow or underflow at all,
/// as all possible output fits in the range of `Duration`.
#[inline]
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> OldDuration {
self.date.signed_duration_since(rhs.date)
}
/// Returns a view to the naive UTC date.
#[inline]
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]
pub fn naive_local(&self) -> NaiveDate {
self.date
}
/// Returns the number of whole years from the given `base` until `self`.
pub fn years_since(&self, base: Self) -> Option<u32> {
self.date.years_since(base.date)
}
/// The minimum possible `Date`.
pub const MIN_UTC: Date<Utc> = Date { date: NaiveDate::MIN, offset: Utc };
/// The maximum possible `Date`.
pub const MAX_UTC: Date<Utc> = Date { date: NaiveDate::MAX, offset: Utc };
}
/// Maps the local date to other date with given conversion function.
fn map_local<Tz: TimeZone, F>(d: &Date<Tz>, mut f: F) -> Option<Date<Tz>>
where
F: FnMut(NaiveDate) -> Option<NaiveDate>,
{
f(d.naive_local()).and_then(|date| d.timezone().from_local_date(&date).single())
}
impl<Tz: TimeZone> Date<Tz>
where
Tz::Offset: fmt::Display,
{
/// Formats the date with the specified formatting items.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
#[inline]
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where
I: Iterator<Item = B> + Clone,
B: Borrow<Item<'a>>,
{
DelayedFormat::new_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", test))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
#[inline]
pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
self.format_with_items(StrftimeItems::new(fmt))
}
/// Formats the date with the specified formatting items and locale.
#[cfg(feature = "unstable-locales")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
#[inline]
pub fn format_localized_with_items<'a, I, B>(
&self,
items: I,
locale: Locale,
) -> DelayedFormat<I>
where
I: Iterator<Item = B> + Clone,
B: Borrow<Item<'a>>,
{
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]
pub fn format_localized<'a>(
&self,
fmt: &'a str,
locale: Locale,
) -> DelayedFormat<StrftimeItems<'a>> {
self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale)
}
}
impl<Tz: TimeZone> Datelike for Date<Tz> {
#[inline]
fn year(&self) -> i32 {
self.naive_local().year()
}
#[inline]
fn month(&self) -> u32 {
self.naive_local().month()
}
#[inline]
fn month0(&self) -> u32 {
self.naive_local().month0()
}
#[inline]
fn day(&self) -> u32 {
self.naive_local().day()
}
#[inline]
fn day0(&self) -> u32 {
self.naive_local().day0()
}
#[inline]
fn ordinal(&self) -> u32 {
self.naive_local().ordinal()
}
#[inline]
fn ordinal0(&self) -> u32 {
self.naive_local().ordinal0()
}
#[inline]
fn weekday(&self) -> Weekday {
self.naive_local().weekday()
}
#[inline]
fn iso_week(&self) -> IsoWeek {
self.naive_local().iso_week()
}
#[inline]
fn with_year(&self, year: i32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_year(year))
}
#[inline]
fn with_month(&self, month: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_month(month))
}
#[inline]
fn with_month0(&self, month0: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_month0(month0))
}
#[inline]
fn with_day(&self, day: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_day(day))
}
#[inline]
fn with_day0(&self, day0: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_day0(day0))
}
#[inline]
fn with_ordinal(&self, ordinal: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_ordinal(ordinal))
}
#[inline]
fn with_ordinal0(&self, ordinal0: u32) -> Option<Date<Tz>> {
map_local(self, |date| date.with_ordinal0(ordinal0))
}
}
// we need them as automatic impls cannot handle associated types
impl<Tz: TimeZone> Copy for Date<Tz> where <Tz as TimeZone>::Offset: Copy {}
unsafe impl<Tz: TimeZone> Send for Date<Tz> where <Tz as TimeZone>::Offset: Send {}
impl<Tz: TimeZone, Tz2: TimeZone> PartialEq<Date<Tz2>> for Date<Tz> {
fn eq(&self, other: &Date<Tz2>) -> bool {
self.date == other.date
}
}
impl<Tz: TimeZone> Eq for Date<Tz> {}
impl<Tz: TimeZone> PartialOrd for Date<Tz> {
fn partial_cmp(&self, other: &Date<Tz>) -> Option<Ordering> {
self.date.partial_cmp(&other.date)
}
}
impl<Tz: TimeZone> Ord for Date<Tz> {
fn cmp(&self, other: &Date<Tz>) -> Ordering {
self.date.cmp(&other.date)
}
}
impl<Tz: TimeZone> hash::Hash for Date<Tz> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.date.hash(state)
}
}
impl<Tz: TimeZone> Add<OldDuration> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
fn add(self, rhs: OldDuration) -> Date<Tz> {
self.checked_add_signed(rhs).expect("`Date + Duration` overflowed")
}
}
impl<Tz: TimeZone> AddAssign<OldDuration> for Date<Tz> {
#[inline]
fn add_assign(&mut self, rhs: OldDuration) {
self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed");
}
}
impl<Tz: TimeZone> Sub<OldDuration> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
fn sub(self, rhs: OldDuration) -> Date<Tz> {
self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed")
}
}
impl<Tz: TimeZone> SubAssign<OldDuration> for Date<Tz> {
#[inline]
fn sub_assign(&mut self, rhs: OldDuration) {
self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed");
}
}
impl<Tz: TimeZone> Sub<Date<Tz>> for Date<Tz> {
type Output = OldDuration;
#[inline]
fn sub(self, rhs: Date<Tz>) -> OldDuration {
self.signed_duration_since(rhs)
}
}
impl<Tz: TimeZone> fmt::Debug for Date<Tz> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.naive_local().fmt(f)?;
self.offset.fmt(f)
}
}
impl<Tz: TimeZone> fmt::Display for Date<Tz>
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<Tz>, due to
// the nontrivial bound <Tz as TimeZone>::Offset: Arbitrary.
#[cfg(feature = "arbitrary")]
impl<'a, Tz> arbitrary::Arbitrary<'a> for Date<Tz>
where
Tz: TimeZone,
<Tz as TimeZone>::Offset: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Date<Tz>> {
let date = NaiveDate::arbitrary(u)?;
let offset = <Tz as TimeZone>::Offset::arbitrary(u)?;
Ok(Date::from_utc(date, offset))
}
}
#[cfg(test)]
mod tests {
use super::Date;
use crate::oldtime::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::<Utc>::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::<Utc>::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));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))]
use super::DateTime;
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc};
use core::fmt;
use core::ops::Deref;
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
impl<Tz: TimeZone> Encodable for DateTime<Tz> {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
format!("{:?}", self).encode(s)
}
}
// lik? function to convert a LocalResult into a serde-ish Result
fn from<T, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
where
D: Decoder,
T: fmt::Display,
{
match me {
LocalResult::None => Err(d.error("value is not a legal timestamp")),
LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")),
LocalResult::Single(val) => Ok(val),
}
}
impl Decodable for DateTime<FixedOffset> {
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<FixedOffset>, D::Error> {
d.read_str()?.parse::<DateTime<FixedOffset>>().map_err(|_| d.error("invalid date and time"))
}
}
#[allow(deprecated)]
impl Decodable for TsSeconds<FixedOffset> {
#[allow(deprecated)]
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<FixedOffset>, D::Error> {
from(FixedOffset::east_opt(0).unwrap().timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
}
}
impl Decodable for DateTime<Utc> {
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Utc>, D::Error> {
d.read_str()?
.parse::<DateTime<FixedOffset>>()
.map(|dt| dt.with_timezone(&Utc))
.map_err(|_| d.error("invalid date and time"))
}
}
/// A [`DateTime`] that can be deserialized from a timestamp
///
/// A timestamp here is seconds since the epoch
#[derive(Debug)]
pub struct TsSeconds<Tz: TimeZone>(DateTime<Tz>);
#[allow(deprecated)]
impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
/// Pull the inner `DateTime<Tz>` out
#[allow(deprecated)]
fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
obj.0
}
}
#[allow(deprecated)]
impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
type Target = DateTime<Tz>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(deprecated)]
impl Decodable for TsSeconds<Utc> {
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Utc>, D::Error> {
from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds)
}
}
#[cfg(feature = "clock")]
#[cfg_attr(docsrs, doc(cfg(feature = "clock")))]
impl Decodable for DateTime<Local> {
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Local>, D::Error> {
match d.read_str()?.parse::<DateTime<FixedOffset>>() {
Ok(dt) => Ok(dt.with_timezone(&Local)),
Err(_) => Err(d.error("invalid date and time")),
}
}
}
#[cfg(feature = "clock")]
#[cfg_attr(docsrs, doc(cfg(feature = "clock")))]
#[allow(deprecated)]
impl Decodable for TsSeconds<Local> {
#[allow(deprecated)]
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Local>, D::Error> {
from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(|dt| TsSeconds(dt.with_timezone(&Local)))
}
}
#[cfg(test)]
use rustc_serialize::json;
#[test]
fn test_encodable() {
super::test_encodable_json(json::encode, json::encode);
}
#[cfg(feature = "clock")]
#[test]
fn test_decodable() {
super::test_decodable_json(json::decode, json::decode, json::decode);
}
#[cfg(feature = "clock")]
#[test]
fn test_decodable_timestamps() {
super::test_decodable_json_timestamps(json::decode, json::decode, json::decode);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,952 @@
use std::time::{SystemTime, UNIX_EPOCH};
use super::DateTime;
use crate::naive::{NaiveDate, NaiveTime};
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, TimeZone, Utc};
use crate::oldtime::Duration;
#[cfg(feature = "clock")]
use crate::Datelike;
use crate::{Days, LocalResult, Months, NaiveDateTime};
#[derive(Clone)]
struct DstTester;
impl DstTester {
fn winter_offset() -> FixedOffset {
FixedOffset::east_opt(8 * 60 * 60).unwrap()
}
fn summer_offset() -> FixedOffset {
FixedOffset::east_opt(9 * 60 * 60).unwrap()
}
const TO_WINTER_MONTH_DAY: (u32, u32) = (4, 15);
const TO_SUMMER_MONTH_DAY: (u32, u32) = (9, 15);
fn transition_start_local() -> NaiveTime {
NaiveTime::from_hms_opt(2, 0, 0).unwrap()
}
}
impl TimeZone for DstTester {
type Offset = FixedOffset;
fn from_offset(_: &Self::Offset) -> Self {
DstTester
}
fn offset_from_local_date(&self, _: &NaiveDate) -> crate::LocalResult<Self::Offset> {
unimplemented!()
}
fn offset_from_local_datetime(
&self,
local: &NaiveDateTime,
) -> crate::LocalResult<Self::Offset> {
let local_to_winter_transition_start = NaiveDate::from_ymd_opt(
local.year(),
DstTester::TO_WINTER_MONTH_DAY.0,
DstTester::TO_WINTER_MONTH_DAY.1,
)
.unwrap()
.and_time(DstTester::transition_start_local());
let local_to_winter_transition_end = NaiveDate::from_ymd_opt(
local.year(),
DstTester::TO_WINTER_MONTH_DAY.0,
DstTester::TO_WINTER_MONTH_DAY.1,
)
.unwrap()
.and_time(DstTester::transition_start_local() - Duration::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() + Duration::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"
);
}
#[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 + Duration::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()),
Duration::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]
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]
fn test_datetime_rfc2822_and_rfc3339() {
let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap();
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, 18, 23, 16, 9).unwrap().to_rfc3339(),
"2015-02-18T23:16:09+00:00"
);
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!(
edt.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!(
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!(
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_rfc3339(),
"2015-02-18T23:59:60.234567+05:00"
);
assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"),
Ok(FixedOffset::east_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!(
DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"),
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: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())
);
}
#[test]
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 = DateTime::<Utc>::from_utc(dt.naive_utc(), Utc);
assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00");
assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z");
assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00");
assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z");
assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z");
assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z");
assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z");
}
#[test]
#[should_panic]
fn test_rfc3339_opts_nonexhaustive() {
use crate::SecondsFormat;
let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap();
dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true);
}
#[test]
fn test_datetime_from_str() {
assert_eq!(
"2015-02-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
Ok(FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-02-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-02-18T23:16:9.15 UTC".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-02-18T23:16:9.15UTC".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
Ok(FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-2-18T13:16:9.15-10:00".parse::<DateTime<FixedOffset>>(),
Ok(FixedOffset::west_opt(10 * 3600)
.unwrap()
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(13, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert!("2015-2-18T23:16:9.15".parse::<DateTime<FixedOffset>>().is_err());
assert_eq!(
"2015-2-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert_eq!(
"2015-2-18T13:16:9.15-10:00".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);
assert!("2015-2-18T23:16:9.15".parse::<DateTime<Utc>>().is_err());
// no test for `DateTime<Local>`, we cannot verify that much.
}
#[test]
fn test_datetime_parse_from_str() {
let ymdhms = |y, m, d, h, n, s, off| {
FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap()
};
assert_eq!(
DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60))
); // ignore offset
assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset
assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT")
.is_err());
assert_eq!(
Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"),
Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap())
);
}
#[test]
fn test_to_string_round_trip() {
let dt = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
let _dt: DateTime<Utc> = dt.to_string().parse().unwrap();
let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap());
let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap());
let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
}
#[test]
#[cfg(feature = "clock")]
fn test_to_string_round_trip_with_local() {
let ndt = Local::now();
let _dt: DateTime<FixedOffset> = ndt.to_string().parse().unwrap();
}
#[test]
#[cfg(feature = "clock")]
fn test_datetime_format_with_local() {
// if we are not around the year boundary, local and UTC date should have the same year
let dt = Local::now().with_month(5).unwrap();
assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string());
}
#[test]
#[cfg(feature = "clock")]
fn test_datetime_is_copy() {
// UTC is known to be `Copy`.
let a = Utc::now();
let b = a;
assert_eq!(a, b);
}
#[test]
#[cfg(feature = "clock")]
fn test_datetime_is_send() {
use std::thread;
// UTC is known to be `Send`.
let a = Utc::now();
thread::spawn(move || {
let _ = a;
})
.join()
.unwrap();
}
#[test]
fn test_subsecond_part() {
let datetime = Utc
.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());
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_from_system_time() {
use std::time::Duration;
let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
let nanos = 999_999_999;
// SystemTime -> DateTime<Utc>
assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
assert_eq!(
DateTime::<Utc>::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)),
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 9, 9)
.unwrap()
.and_hms_nano_opt(1, 46, 39, nanos)
.unwrap()
)
.unwrap()
);
assert_eq!(
DateTime::<Utc>::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)),
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap()
)
.unwrap()
);
// DateTime<Utc> -> SystemTime
assert_eq!(SystemTime::from(epoch), UNIX_EPOCH);
assert_eq!(
SystemTime::from(
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 9, 9)
.unwrap()
.and_hms_nano_opt(1, 46, 39, nanos)
.unwrap()
)
.unwrap()
),
UNIX_EPOCH + Duration::new(999_999_999, nanos)
);
assert_eq!(
SystemTime::from(
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(1938, 4, 24)
.unwrap()
.and_hms_nano_opt(22, 13, 20, 1)
.unwrap()
)
.unwrap()
),
UNIX_EPOCH - Duration::new(999_999_999, 999_999_999)
);
// DateTime<any tz> -> SystemTime (via `with_timezone`)
#[cfg(feature = "clock")]
{
assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH);
}
assert_eq!(
SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())),
UNIX_EPOCH
);
assert_eq!(
SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())),
UNIX_EPOCH
);
}
#[test]
#[cfg(target_os = "windows")]
fn test_from_system_time() {
use std::time::Duration;
let nanos = 999_999_000;
let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
// SystemTime -> DateTime<Utc>
assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
assert_eq!(
DateTime::<Utc>::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)),
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 9, 9)
.unwrap()
.and_hms_nano_opt(1, 46, 39, nanos)
.unwrap()
)
.unwrap()
);
assert_eq!(
DateTime::<Utc>::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)),
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(1938, 4, 24)
.unwrap()
.and_hms_nano_opt(22, 13, 20, 1_000)
.unwrap()
)
.unwrap()
);
// DateTime<Utc> -> SystemTime
assert_eq!(SystemTime::from(epoch), UNIX_EPOCH);
assert_eq!(
SystemTime::from(
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 9, 9)
.unwrap()
.and_hms_nano_opt(1, 46, 39, nanos)
.unwrap()
)
.unwrap()
),
UNIX_EPOCH + Duration::new(999_999_999, nanos)
);
assert_eq!(
SystemTime::from(
Utc.from_local_datetime(
&NaiveDate::from_ymd_opt(1938, 4, 24)
.unwrap()
.and_hms_nano_opt(22, 13, 20, 1_000)
.unwrap()
)
.unwrap()
),
UNIX_EPOCH - Duration::new(999_999_999, nanos)
);
// DateTime<any tz> -> SystemTime (via `with_timezone`)
#[cfg(feature = "clock")]
{
assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH);
}
assert_eq!(
SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())),
UNIX_EPOCH
);
assert_eq!(
SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())),
UNIX_EPOCH
);
}
#[test]
fn test_datetime_format_alignment() {
let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap();
// Item::Literal
let percent = datetime.format("%%");
assert_eq!(" %", format!("{:>3}", percent));
assert_eq!("% ", format!("{:<3}", percent));
assert_eq!(" % ", format!("{:^3}", percent));
// Item::Numeric
let year = datetime.format("%Y");
assert_eq!(" 2007", format!("{:>6}", year));
assert_eq!("2007 ", format!("{:<6}", year));
assert_eq!(" 2007 ", format!("{:^6}", year));
// Item::Fixed
let tz = datetime.format("%Z");
assert_eq!(" UTC", format!("{:>5}", tz));
assert_eq!("UTC ", format!("{:<5}", tz));
assert_eq!(" UTC ", format!("{:^5}", tz));
// [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric]
let ymd = datetime.format("%Y %B %d");
let ymd_formatted = "2007 January 02";
assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd));
assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd));
assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd));
}
#[test]
fn test_datetime_from_local() {
// 2000-01-12T02:00:00Z
let naivedatetime_utc =
NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap();
let datetime_utc = DateTime::<Utc>::from_utc(naivedatetime_utc, Utc);
// 2000-01-12T10:00:00+8:00:00
let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap();
let naivedatetime_east =
NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap();
let datetime_east = DateTime::<FixedOffset>::from_local(naivedatetime_east, timezone_east);
// 2000-01-11T19:00:00-7:00:00
let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap();
let naivedatetime_west =
NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap();
let datetime_west = DateTime::<FixedOffset>::from_local(naivedatetime_west, timezone_west);
assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east));
assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west));
}
#[test]
#[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() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
// A bit more than 2 years.
let two_year_ago =
Utc::now().date_naive() - Duration::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() + Duration::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 = DateTime::<Utc>::from_utc(naivedatetime, Utc);
let mut datetime_add = datetime;
datetime_add += Duration::seconds(60);
assert_eq!(datetime_add, datetime + Duration::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 + Duration::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 + Duration::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 += Duration::days(1);
assert_eq!(datetime_add, datetime + Duration::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 = DateTime::<Utc>::from_utc(naivedatetime, Utc);
let mut datetime_sub = datetime;
datetime_sub -= Duration::minutes(90);
assert_eq!(datetime_sub, datetime - Duration::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 - Duration::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 - Duration::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 -= Duration::days(1);
assert_eq!(datetime_sub, datetime - Duration::days(i))
}
}

View File

@@ -0,0 +1,33 @@
use pure_rust_locales::{locale_match, Locale};
pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::ABMON)
}
pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::MON)
}
pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::ABDAY)
}
pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::DAY)
}
pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::AM_PM)
}
pub(crate) fn d_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::D_FMT)
}
pub(crate) fn d_t_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::D_T_FMT)
}
pub(crate) fn t_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::T_FMT)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,977 @@
// 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.
#![allow(deprecated)]
use core::borrow::Borrow;
use core::str;
use core::usize;
use super::scan;
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed};
use super::{ParseError, ParseErrorKind, ParseResult};
use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT};
use 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 = <any char except '(', ')' and '\\'>
// c-escape = "\" <any char>
// comment = "(" *(comment / c-char / c-escape) ")" *S
// date-time = [ day-of-week "," ] date 1*S time *S *comment
// day-of-week = *S day-name *S
// day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
// date = day month year
// 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_left();
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_left();
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_left(), b':')?.trim_left(); // *S ":" *S
parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
if let Ok(s_) = scan::char(s.trim_left(), 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, ()))
}
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.
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') | Some(&b'T') => &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_zulu(s, |s| scan::char(s, b':')));
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<Item = B>,
B: Borrow<Item<'a>>,
{
parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| 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<Item = B>,
B: Borrow<Item<'a>>,
{
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", test))]
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_left();
}
#[cfg(any(feature = "alloc", feature = "std", test))]
Item::OwnedSpace(_) => {
s = s.trim_left();
}
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_left();
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!(scan::timezone_name_skip(s));
}
&TimezoneOffsetColon
| &TimezoneOffsetDoubleColon
| &TimezoneOffsetTripleColon
| &TimezoneOffset => {
let offset = try_consume!(scan::timezone_offset(
s.trim_left(),
scan::colon_or_space
));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}
&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
let offset = try_consume!(scan::timezone_offset_zulu(
s.trim_left(),
scan::colon_or_space
));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}
&Internal(InternalFixed {
val: InternalInternal::TimezoneOffsetPermissive,
}) => {
let offset = try_consume!(scan::timezone_offset_permissive(
s.trim_left(),
scan::colon_or_space
));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}
&RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
&RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
}
}
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::<DateTime<FixedOffset>>();
/// "2012-12-12 12:12:12Z".parse::<DateTime<FixedOffset>>();
/// "2012- 12-12T12: 12:12Z".parse::<DateTime<FixedOffset>>();
/// ```
impl str::FromStr for DateTime<FixedOffset> {
type Err = ParseError;
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
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(""),
Item::Fixed(Fixed::TimezoneOffsetZ),
Item::Space(""),
];
let mut parsed = Parsed::new();
match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) {
Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => {
if remainder.starts_with('T') || remainder.starts_with(' ') {
parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?;
} else {
return Err(INVALID);
}
}
Err((_s, e)) => return Err(e),
Ok(_) => return Err(NOT_ENOUGH),
};
parsed.to_datetime()
}
}
#[cfg(test)]
#[test]
fn test_parse() {
use super::IMPOSSIBLE;
use super::*;
// workaround for Rust issue #22255
fn parse_all(s: &str, items: &[Item]) -> ParseResult<Parsed> {
let mut parsed = Parsed::new();
parse(&mut parsed, s, items.iter())?;
Ok(parsed)
}
macro_rules! check {
($fmt:expr, $items:expr; $err:tt) => (
assert_eq!(parse_all($fmt, &$items), Err($err))
);
($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] {
let mut expected = Parsed::new();
$(expected.$k = Some($v);)*
assert_eq!(parse_all($fmt, &$items), Ok(expected))
});
}
// empty string
check!("", []; );
check!(" ", []; TOO_LONG);
check!("a", []; TOO_LONG);
// whitespaces
check!("", [sp!("")]; );
check!(" ", [sp!("")]; );
check!("\t", [sp!("")]; );
check!(" \n\r \n", [sp!("")]; );
check!("a", [sp!("")]; TOO_LONG);
// literal
check!("", [lit!("a")]; TOO_SHORT);
check!(" ", [lit!("a")]; INVALID);
check!("a", [lit!("a")]; );
check!("aa", [lit!("a")]; TOO_LONG);
check!("A", [lit!("a")]; INVALID);
check!("xy", [lit!("xy")]; );
check!("xy", [lit!("x"), lit!("y")]; );
check!("x y", [lit!("x"), lit!("y")]; INVALID);
check!("xy", [lit!("x"), sp!(""), lit!("y")]; );
check!("x y", [lit!("x"), sp!(""), lit!("y")]; );
// numeric
check!("1987", [num!(Year)]; year: 1987);
check!("1987 ", [num!(Year)]; TOO_LONG);
check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed
check!("x123", [num!(Year)]; INVALID);
check!("2015", [num!(Year)]; year: 2015);
check!("0000", [num!(Year)]; year: 0);
check!("9999", [num!(Year)]; year: 9999);
check!(" \t987", [num!(Year)]; year: 987);
check!("5", [num!(Year)]; year: 5);
check!("5\0", [num!(Year)]; TOO_LONG);
check!("\x005", [num!(Year)]; INVALID);
check!("", [num!(Year)]; TOO_SHORT);
check!("12345", [num!(Year), lit!("5")]; year: 1234);
check!("12345", [nums!(Year), lit!("5")]; year: 1234);
check!("12345", [num0!(Year), lit!("5")]; year: 1234);
check!("12341234", [num!(Year), num!(Year)]; year: 1234);
check!("1234 1234", [num!(Year), num!(Year)]; year: 1234);
check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE);
check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234);
check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
// signed numeric
check!("-42", [num!(Year)]; year: -42);
check!("+42", [num!(Year)]; year: 42);
check!("-0042", [num!(Year)]; year: -42);
check!("+0042", [num!(Year)]; year: 42);
check!("-42195", [num!(Year)]; year: -42195);
check!("+42195", [num!(Year)]; year: 42195);
check!(" -42195", [num!(Year)]; year: -42195);
check!(" +42195", [num!(Year)]; year: 42195);
check!(" - 42", [num!(Year)]; INVALID);
check!(" + 42", [num!(Year)]; INVALID);
check!("-", [num!(Year)]; TOO_SHORT);
check!("+", [num!(Year)]; TOO_SHORT);
// unsigned numeric
check!("345", [num!(Ordinal)]; ordinal: 345);
check!("+345", [num!(Ordinal)]; INVALID);
check!("-345", [num!(Ordinal)]; INVALID);
check!(" 345", [num!(Ordinal)]; ordinal: 345);
check!(" +345", [num!(Ordinal)]; INVALID);
check!(" -345", [num!(Ordinal)]; INVALID);
// various numeric fields
check!("1234 5678",
[num!(Year), num!(IsoYear)];
year: 1234, isoyear: 5678);
check!("12 34 56 78",
[num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)];
year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78);
check!("1 2 3 4 5 6",
[num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek),
num!(NumDaysFromSun)];
month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat);
check!("7 89 01",
[num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)];
weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1);
check!("23 45 6 78901234 567890123",
[num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)];
hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234,
timestamp: 567_890_123);
// fixed: month and weekday names
check!("apr", [fix!(ShortMonthName)]; month: 4);
check!("Apr", [fix!(ShortMonthName)]; month: 4);
check!("APR", [fix!(ShortMonthName)]; month: 4);
check!("ApR", [fix!(ShortMonthName)]; month: 4);
check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed
check!("A", [fix!(ShortMonthName)]; TOO_SHORT);
check!("Sol", [fix!(ShortMonthName)]; INVALID);
check!("Apr", [fix!(LongMonthName)]; month: 4);
check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed
check!("April", [fix!(LongMonthName)]; month: 4);
check!("Aprill", [fix!(LongMonthName)]; TOO_LONG);
check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4);
check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4);
check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack
check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed
check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT);
check!("The", [fix!(ShortWeekdayName)]; INVALID);
check!("Nop", [fix!(ShortWeekdayName)]; INVALID);
check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed
check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto
check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG);
check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack
// fixed: am/pm
check!("am", [fix!(LowerAmPm)]; hour_div_12: 0);
check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1);
check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0);
check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1);
check!("am", [fix!(UpperAmPm)]; hour_div_12: 0);
check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1);
check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0);
check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1);
check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0);
check!(" Am", [fix!(LowerAmPm)]; INVALID);
check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed
check!("a", [fix!(LowerAmPm)]; TOO_SHORT);
check!("p", [fix!(LowerAmPm)]; TOO_SHORT);
check!("x", [fix!(LowerAmPm)]; TOO_SHORT);
check!("xx", [fix!(LowerAmPm)]; INVALID);
check!("", [fix!(LowerAmPm)]; TOO_SHORT);
// fixed: dot plus nanoseconds
check!("", [fix!(Nanosecond)]; ); // no field set, but not an error
check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4`
check!("4", [fix!(Nanosecond), num!(Second)]; second: 4);
check!(".0", [fix!(Nanosecond)]; nanosecond: 0);
check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000);
check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000);
check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000);
check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000);
check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803);
check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803);
check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3);
check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0);
check!(".", [fix!(Nanosecond)]; TOO_SHORT);
check!(".4x", [fix!(Nanosecond)]; TOO_LONG);
check!(". 4", [fix!(Nanosecond)]; INVALID);
check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
// fixed: nanoseconds without the dot
check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000);
check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43);
check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG);
check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID);
check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID);
check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000);
check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000);
check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0);
check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID);
check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID);
check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803);
check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3);
check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9
check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG);
check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0);
check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID);
check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID);
check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID);
// fixed: timezone offsets
check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
check!("-00:00", [fix!(TimezoneOffset)]; offset: 0);
check!("+00:01", [fix!(TimezoneOffset)]; offset: 60);
check!("-00:01", [fix!(TimezoneOffset)]; offset: -60);
check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60);
check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60);
check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60);
check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60);
check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60);
check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60);
check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60);
check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60);
check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60);
check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
check!("#12:34", [fix!(TimezoneOffset)]; INVALID);
check!("12:34", [fix!(TimezoneOffset)]; INVALID);
check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG);
check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60);
check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60);
check!("", [fix!(TimezoneOffset)]; TOO_SHORT);
check!("+", [fix!(TimezoneOffset)]; TOO_SHORT);
check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT);
check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT);
check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT);
check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60);
check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5);
check!("Z", [fix!(TimezoneOffset)]; INVALID);
check!("z", [fix!(TimezoneOffset)]; INVALID);
check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0);
check!("z", [fix!(TimezoneOffsetZ)]; offset: 0);
check!("Y", [fix!(TimezoneOffsetZ)]; INVALID);
check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5);
// some practical examples
check!("2015-02-04T14:37:05+09:00",
[num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"),
num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)];
year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
minute: 37, second: 5, offset: 32400);
check!("20150204143705567",
[num!(Year), num!(Month), num!(Day),
num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)];
year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
minute: 37, second: 5, nanosecond: 567000000);
check!("Mon, 10 Jun 2013 09:32:37 GMT",
[fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "),
fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"),
num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")];
year: 2013, month: 6, day: 10, weekday: Weekday::Mon,
hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37);
check!("Sun Aug 02 13:39:15 CEST 2020",
[fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "),
num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"),
num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)];
year: 2020, month: 8, day: 2, weekday: Weekday::Sun,
hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15);
check!("20060102150405",
[num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)];
year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5);
check!("3:14PM",
[num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)];
hour_div_12: 1, hour_mod_12: 3, minute: 14);
check!("12345678901234.56789",
[num!(Timestamp), lit!("."), num!(Nanosecond)];
nanosecond: 56_789, timestamp: 12_345_678_901_234);
check!("12345678901234.56789",
[num!(Timestamp), fix!(Nanosecond)];
nanosecond: 567_890_000, timestamp: 12_345_678_901_234);
}
#[cfg(test)]
#[test]
fn test_rfc2822() {
use super::NOT_ENOUGH;
use super::*;
use crate::offset::FixedOffset;
use crate::DateTime;
// Test data - (input, Ok(expected result after parse and format) or Err(error code))
let testdates = [
("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case
("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace
("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero
("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment
(
r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))",
Ok("Tue, 20 Jan 2015 17:35:20 -0800"),
), // 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("Tue, 20 Jan 2015 17:35:20 -0800"),
), // 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("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week
("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month
("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second
("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")),
("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month
("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields
("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name
("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour
("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour
("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute
("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second
("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset
("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed)
("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone
];
fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
let mut parsed = Parsed::new();
parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?;
parsed.to_datetime()
}
fn fmt_rfc2822_datetime(dt: DateTime<FixedOffset>) -> String {
dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string()
}
// Test against test data above
for &(date, checkdate) in testdates.iter() {
let d = rfc2822_to_datetime(date); // parse a date
let dt = match d {
// did we get a value?
Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on
Err(e) => Err(e), // otherwise keep an error for the comparison
};
if dt != checkdate.map(|s| s.to_string()) {
// check for expected result
panic!(
"Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
date, dt, checkdate
);
}
}
}
#[cfg(test)]
#[test]
fn parse_rfc850() {
use crate::{TimeZone, Utc};
static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT";
let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT";
let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap();
// Check that the format is what we expect
assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str);
// Check that it parses correctly
assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT));
// Check that the rest of the weekdays parse correctly (this test originally failed because
// Sunday parsed incorrectly).
let testdates = [
(Utc.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!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT));
}
}
#[cfg(test)]
#[test]
fn test_rfc3339() {
use super::*;
use crate::offset::FixedOffset;
use crate::DateTime;
// Test data - (input, Ok(expected result after parse and format) or Err(error code))
let testdates = [
("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case
("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day
("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")),
("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")),
("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")),
("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")),
("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small
("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month
("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour
("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute
("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second
("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset
];
fn rfc3339_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
let mut parsed = Parsed::new();
parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?;
parsed.to_datetime()
}
fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String {
dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string()
}
// Test against test data above
for &(date, checkdate) in testdates.iter() {
let d = rfc3339_to_datetime(date); // parse a date
let dt = match d {
// did we get a value?
Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on
Err(e) => Err(e), // otherwise keep an error for the comparison
};
if dt != checkdate.map(|s| s.to_string()) {
// check for expected result
panic!(
"Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
date, dt, checkdate
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,415 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
/*!
* Various scanning routines for the parser.
*/
#![allow(deprecated)]
use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
use crate::Weekday;
/// Returns true when two slices are equal case-insensitively (in ASCII).
/// Assumes that the `pattern` is already converted to lower case.
fn equals(s: &str, pattern: &str) -> bool {
let mut xs = s.as_bytes().iter().map(|&c| match c {
b'A'..=b'Z' => c + 32,
_ => c,
});
let mut ys = pattern.as_bytes().iter().cloned();
loop {
match (xs.next(), ys.next()) {
(None, None) => return true,
(None, _) | (_, None) => return false,
(Some(x), Some(y)) if x != y => return false,
_ => (),
}
}
}
/// 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 !(b'0'..=b'9').contains(&c) {
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_left_matches(|c: char| ('0'..='9').contains(&c));
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: [&str; 12] =
["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
let (mut s, month0) = short_month0(s)?;
// tries to consume the suffix if possible
let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
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: [&str; 7] =
["day", "sday", "nesday", "rsday", "day", "urday", "day"];
let (mut s, weekday) = short_weekday(s)?;
// tries to consume the suffix if possible
let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
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_left();
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(super) fn colon_or_space(s: &str) -> ParseResult<&str> {
Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
}
/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
///
/// The additional `colon` may be used to parse a mandatory or optional `:`
/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
pub(super) fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
timezone_offset_internal(s, consume_colon, false)
}
fn timezone_offset_internal<F>(
mut s: &str,
mut consume_colon: F,
allow_missing_minutes: bool,
) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
fn digits(s: &str) -> ParseResult<(u8, u8)> {
let b = s.as_bytes();
if b.len() < 2 {
Err(TOO_SHORT)
} else {
Ok((b[0], b[1]))
}
}
let negative = match s.as_bytes().first() {
Some(&b'+') => false,
Some(&b'-') => true,
Some(_) => return Err(INVALID),
None => return Err(TOO_SHORT),
};
s = &s[1..];
// hours (00--99)
let hours = match digits(s)? {
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
_ => 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 `z`/`Z` which is the same as `+00:00`.
pub(super) fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
let bytes = s.as_bytes();
match bytes.first() {
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
Some(&b'u') | Some(&b'U') => {
if bytes.len() >= 3 {
let (b, c) = (bytes[1], bytes[2]);
match (b | 32, c | 32) {
(b't', b'c') => Ok((&s[3..], 0)),
_ => Err(INVALID),
}
} else {
Err(INVALID)
}
}
_ => timezone_offset(s, colon),
}
}
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as
/// `+00:00`, and allows missing minutes entirely.
pub(super) fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
match s.as_bytes().first() {
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
_ => timezone_offset_internal(s, colon, true),
}
}
/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones.
/// May return `None` which indicates an insufficient offset data (i.e. `-0000`).
pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> {
// tries to parse legacy time zone names
let upto = s
.as_bytes()
.iter()
.position(|&c| match c {
b'a'..=b'z' | b'A'..=b'Z' => false,
_ => true,
})
.unwrap_or(s.len());
if upto > 0 {
let name = &s[..upto];
let s = &s[upto..];
let offset_hours = |o| Ok((s, Some(o * 3600)));
if equals(name, "gmt") || equals(name, "ut") {
offset_hours(0)
} else if equals(name, "edt") {
offset_hours(-4)
} else if equals(name, "est") || equals(name, "cdt") {
offset_hours(-5)
} else if equals(name, "cst") || equals(name, "mdt") {
offset_hours(-6)
} else if equals(name, "mst") || equals(name, "pdt") {
offset_hours(-7)
} else if equals(name, "pst") {
offset_hours(-8)
} else {
Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000
}
} else {
let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
Ok((s_, Some(offset)))
}
}
/// Tries to consume everything until next whitespace-like symbol.
/// Does not provide any offset information from the consumed data.
pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
}
/// 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)]
#[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
);
}
}

View File

@@ -0,0 +1,712 @@
// 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. |
| `%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` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] |
| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] |
| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] |
| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] |
| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] |
| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] |
| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] |
| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] |
| | | |
| `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
| `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
| `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). |
| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. |
| | | |
| | | **TIME ZONE SPECIFIERS:** |
| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^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.
<br>
<br>
This format also supports having a `Z` or `UTC` in place of `%:z`. They
are equivalent to `+00:00`.
<br>
<br>
Note that all `T`, `Z`, and `UTC` are parsed case-insensitively.
<br>
<br>
The typical `strftime` implementations have different (and locale-dependent)
formats for this specifier. While Chrono's format for `%+` is far more
stable, it is best to avoid this specifier if you want to control the exact
output.
[^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`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
<br>
The default `%f` is right-aligned and always zero-padded to 9 digits
for the compatibility with glibc and others,
so it always counts the number of nanoseconds since the last whole second.
E.g. 7ms after the last second will print `007000000`,
and parsing `7000000` will yield the same.
<br>
<br>
The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits
according to the precision.
E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`),
and parsing `.07`, `.070000` etc. will yield the same.
Note that they can print or read nothing if the fractional part is zero or
the next character is not `.`.
<br>
<br>
The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits
according to the number preceding `f`.
E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`),
and parsing `.07`, `.070000` etc. will yield the same.
Note that they can read nothing if the fractional part is zero or
the next character is not `.` however will print with the specified length.
<br>
<br>
The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits
according to the number preceding `f`, but without the leading dot.
E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`),
and parsing `07`, `070000` etc. will yield the same.
Note that they can read nothing if the fractional part is zero.
[^8]: `%Z`:
Offset will not be populated from the parsed data, nor will it be validated.
Timezone is completely ignored. Similar to the glibc `strptime` treatment of
this format code.
<br>
<br>
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.
*/
#[cfg(feature = "unstable-locales")]
extern crate alloc;
#[cfg(feature = "unstable-locales")]
use alloc::vec::Vec;
#[cfg(feature = "unstable-locales")]
use super::{locales, Locale};
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad};
#[cfg(feature = "unstable-locales")]
type Fmt<'a> = Vec<Item<'a>>;
#[cfg(not(feature = "unstable-locales"))]
type Fmt<'a> = &'static [Item<'static>];
static D_FMT: &[Item<'static>] =
&[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)];
static D_T_FMT: &[Item<'static>] = &[
fix!(ShortWeekdayName),
sp!(" "),
fix!(ShortMonthName),
sp!(" "),
nums!(Day),
sp!(" "),
num0!(Hour),
lit!(":"),
num0!(Minute),
lit!(":"),
num0!(Second),
sp!(" "),
num0!(Year),
];
static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)];
/// 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. `%+`),
/// parser refers to the statically reconstructed slice of them.
/// If `recons` is not empty they have to be returned earlier than the `remainder`.
recons: Fmt<'a>,
/// Date format
d_fmt: Fmt<'a>,
/// Date and time format
d_t_fmt: Fmt<'a>,
/// Time format
t_fmt: Fmt<'a>,
}
impl<'a> StrftimeItems<'a> {
/// Creates a new parsing iterator from the `strftime`-like format string.
pub fn new(s: &'a str) -> StrftimeItems<'a> {
Self::with_remainer(s)
}
/// Creates a new parsing iterator from the `strftime`-like format string.
#[cfg(feature = "unstable-locales")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect();
let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect();
let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect();
StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt }
}
#[cfg(not(feature = "unstable-locales"))]
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
static FMT_NONE: &[Item<'static>; 0] = &[];
StrftimeItems {
remainder: s,
recons: FMT_NONE,
d_fmt: D_FMT,
d_t_fmt: D_T_FMT,
t_fmt: T_FMT,
}
}
#[cfg(feature = "unstable-locales")]
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
StrftimeItems {
remainder: s,
recons: Vec::new(),
d_fmt: D_FMT.to_vec(),
d_t_fmt: D_T_FMT.to_vec(),
t_fmt: T_FMT.to_vec(),
}
}
}
const HAVE_ALTERNATES: &str = "z";
impl<'a> Iterator for StrftimeItems<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
// we have some reconstructed items to return
if !self.recons.is_empty() {
let item;
#[cfg(feature = "unstable-locales")]
{
item = self.recons.remove(0);
}
#[cfg(not(feature = "unstable-locales"))]
{
item = self.recons[0].clone();
self.recons = &self.recons[1..];
}
return Some(item);
}
match self.remainder.chars().next() {
// we are done
None => None,
// the next item is a specifier
Some('%') => {
self.remainder = &self.remainder[1..];
macro_rules! next {
() => {
match self.remainder.chars().next() {
Some(x) => {
self.remainder = &self.remainder[x.len_utf8()..];
x
}
None => return Some(Item::Error), // premature end of string
}
};
}
let spec = next!();
let pad_override = match spec {
'-' => Some(Pad::None),
'0' => Some(Pad::Zero),
'_' => Some(Pad::Space),
_ => None,
};
let is_alternate = spec == '#';
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
return Some(Item::Error);
}
macro_rules! recons {
[$head:expr, $($tail:expr),+ $(,)*] => ({
#[cfg(feature = "unstable-locales")]
{
self.recons.clear();
$(self.recons.push($tail);)+
}
#[cfg(not(feature = "unstable-locales"))]
{
const RECONS: &'static [Item<'static>] = &[$($tail),+];
self.recons = RECONS;
}
$head
})
}
macro_rules! recons_from_slice {
($slice:expr) => {{
#[cfg(feature = "unstable-locales")]
{
self.recons.clear();
self.recons.extend_from_slice(&$slice[1..]);
}
#[cfg(not(feature = "unstable-locales"))]
{
self.recons = &$slice[1..];
}
$slice[0].clone()
}};
}
let item = match spec {
'A' => fix!(LongWeekdayName),
'B' => fix!(LongMonthName),
'C' => num0!(YearDiv100),
'D' => {
recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]
}
'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
'G' => num0!(IsoYear),
'H' => num0!(Hour),
'I' => num0!(Hour12),
'M' => num0!(Minute),
'P' => fix!(LowerAmPm),
'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
'S' => num0!(Second),
'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
'U' => num0!(WeekFromSun),
'V' => num0!(IsoWeek),
'W' => num0!(WeekFromMon),
'X' => recons_from_slice!(self.t_fmt),
'Y' => num0!(Year),
'Z' => fix!(TimezoneName),
'a' => fix!(ShortWeekdayName),
'b' | 'h' => fix!(ShortMonthName),
'c' => recons_from_slice!(self.d_t_fmt),
'd' => num0!(Day),
'e' => nums!(Day),
'f' => num0!(Nanosecond),
'g' => num0!(IsoYearMod100),
'j' => num0!(Ordinal),
'k' => nums!(Hour),
'l' => nums!(Hour12),
'm' => num0!(Month),
'n' => sp!("\n"),
'p' => fix!(UpperAmPm),
'r' => recons![
num0!(Hour12),
lit!(":"),
num0!(Minute),
lit!(":"),
num0!(Second),
sp!(" "),
fix!(UpperAmPm)
],
's' => num!(Timestamp),
't' => sp!("\t"),
'u' => num!(WeekdayFromMon),
'v' => {
recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)]
}
'w' => num!(NumDaysFromSun),
'x' => recons_from_slice!(self.d_fmt),
'y' => num0!(YearMod100),
'z' => {
if is_alternate {
internal_fix!(TimezoneOffsetPermissive)
} else {
fix!(TimezoneOffset)
}
}
'+' => fix!(RFC3339),
':' => {
if self.remainder.starts_with("::z") {
self.remainder = &self.remainder[3..];
fix!(TimezoneOffsetTripleColon)
} else if self.remainder.starts_with(":z") {
self.remainder = &self.remainder[2..];
fix!(TimezoneOffsetDoubleColon)
} else if self.remainder.starts_with('z') {
self.remainder = &self.remainder[1..];
fix!(TimezoneOffsetColon)
} else {
Item::Error
}
}
'.' => match next!() {
'3' => match next!() {
'f' => fix!(Nanosecond3),
_ => Item::Error,
},
'6' => match next!() {
'f' => fix!(Nanosecond6),
_ => Item::Error,
},
'9' => match next!() {
'f' => fix!(Nanosecond9),
_ => Item::Error,
},
'f' => fix!(Nanosecond),
_ => Item::Error,
},
'3' => match next!() {
'f' => internal_fix!(Nanosecond3NoDot),
_ => Item::Error,
},
'6' => match next!() {
'f' => internal_fix!(Nanosecond6NoDot),
_ => Item::Error,
},
'9' => match next!() {
'f' => internal_fix!(Nanosecond9NoDot),
_ => Item::Error,
},
'%' => lit!("%"),
_ => Item::Error, // no such specifier
};
// adjust `item` if we have any padding modifier
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(ref kind, _pad) if self.recons.is_empty() => {
Some(Item::Numeric(kind.clone(), new_pad))
}
_ => Some(Item::Error), // no reconstructed or non-numeric item allowed
}
} else {
Some(item)
}
}
// the next item is space
Some(c) if c.is_whitespace() => {
// `%` is not a whitespace, so `c != '%'` is redundant
let nextspec = self
.remainder
.find(|c: char| !c.is_whitespace())
.unwrap_or(self.remainder.len());
assert!(nextspec > 0);
let item = sp!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
}
// the next item is literal
_ => {
let nextspec = self
.remainder
.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or(self.remainder.len());
assert!(nextspec > 0);
let item = lit!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
}
}
}
}
#[cfg(test)]
#[test]
fn test_strftime_items() {
fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
// map any error into `[Item::Error]`. useful for easy testing.
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
}
assert_eq!(parse_and_collect(""), []);
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
assert_eq!(
parse_and_collect("a b\t\nc"),
[lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
);
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
assert_eq!(
parse_and_collect("%Y-%m-%d"),
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}
#[cfg(test)]
#[test]
fn test_strftime_docs() {
use crate::NaiveDate;
use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc};
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(), "28");
assert_eq!(dt.format("%W").to_string(), "27");
assert_eq!(dt.format("%G").to_string(), "2001");
assert_eq!(dt.format("%g").to_string(), "01");
assert_eq!(dt.format("%V").to_string(), "27");
assert_eq!(dt.format("%j").to_string(), "189");
assert_eq!(dt.format("%D").to_string(), "07/08/01");
assert_eq!(dt.format("%x").to_string(), "07/08/01");
assert_eq!(dt.format("%F").to_string(), "2001-07-08");
assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
// time specifiers
assert_eq!(dt.format("%H").to_string(), "00");
assert_eq!(dt.format("%k").to_string(), " 0");
assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
assert_eq!(dt.format("%I").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
assert_eq!(dt.format("%P").to_string(), "am");
assert_eq!(dt.format("%p").to_string(), "AM");
assert_eq!(dt.format("%M").to_string(), "34");
assert_eq!(dt.format("%S").to_string(), "60");
assert_eq!(dt.format("%f").to_string(), "026490708");
assert_eq!(dt.format("%.f").to_string(), ".026490708");
assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
assert_eq!(dt.format("%.3f").to_string(), ".026");
assert_eq!(dt.format("%.6f").to_string(), ".026490");
assert_eq!(dt.format("%.9f").to_string(), ".026490708");
assert_eq!(dt.format("%3f").to_string(), "026");
assert_eq!(dt.format("%6f").to_string(), "026490");
assert_eq!(dt.format("%9f").to_string(), "026490708");
assert_eq!(dt.format("%R").to_string(), "00:34");
assert_eq!(dt.format("%T").to_string(), "00:34:60");
assert_eq!(dt.format("%X").to_string(), "00:34:60");
assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
// time zone specifiers
//assert_eq!(dt.format("%Z").to_string(), "ACST");
assert_eq!(dt.format("%z").to_string(), "+0930");
assert_eq!(dt.format("%:z").to_string(), "+09:30");
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(), "%");
}
#[cfg(feature = "unstable-locales")]
#[test]
fn test_strftime_docs_localized() {
use crate::{FixedOffset, NaiveDate, TimeZone};
let dt = FixedOffset::east_opt(34200).unwrap().ymd_opt(2001, 7, 8).unwrap().and_hms_nano(
0,
34,
59,
1_026_490_708,
);
// date specifiers
assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
// time specifiers
assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
// date & time specifiers
assert_eq!(
dt.format_localized("%c", Locale::fr_BE).to_string(),
"dim 08 jui 2001 00:34:60 +09:30"
);
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");
}

View File

@@ -0,0 +1,527 @@
//! # Chrono: Date and Time for Rust
//!
//! It aims to be a feature-complete superset of
//! the [time](https://github.com/rust-lang-deprecated/time) library.
//! In particular,
//!
//! * Chrono strictly adheres to ISO 8601.
//! * Chrono is timezone-aware by default, with separate timezone-naive types.
//! * Chrono is space-optimal and (while not being the primary goal) reasonably efficient.
//!
//! There were several previous attempts to bring a good date and time library to Rust,
//! which Chrono builds upon and should acknowledge:
//!
//! * [Initial research on
//! the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md)
//! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs)
//! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime)
//!
//! ### 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.
//!
//! Optional features:
//!
//! - [`serde`][]: Enable serialization/deserialization via serde.
//! - `unstable-locales`: Enable localization. This adds various methods with a
//! `_localized` suffix. The implementation and API may change or even be
//! removed in a patch release. Feedback welcome.
//!
//! [`serde`]: https://github.com/serde-rs/serde
//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen
//!
//! See the [cargo docs][] for examples of specifying features.
//!
//! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features
//!
//! ## Overview
//!
//! ### Duration
//!
//! Chrono currently uses its own [`Duration`] type to represent the magnitude
//! of a time span. Since this has the same name as the newer, standard type for
//! duration, the reference will refer this type as `OldDuration`.
//!
//! Note that this is an "accurate" duration represented as seconds and
//! nanoseconds and does not represent "nominal" components such as days or
//! months.
//!
//! When the `oldtime` feature is enabled, [`Duration`] is an alias for the
//! [`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html)
//! type from v0.1 of the time crate. time v0.1 is deprecated, so new code
//! should disable the `oldtime` feature and use the `chrono::Duration` type
//! instead. The `oldtime` feature is enabled by default for backwards
//! compatibility, but future versions of Chrono are likely to remove the
//! feature entirely.
//!
//! Chrono does not yet natively support
//! the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type,
//! but it will be supported in the future.
//! Meanwhile you can convert between two types with
//! [`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std)
//! and
//! [`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std)
//! methods.
//!
//! ### Date and Time
//!
//! Chrono provides a
//! [**`DateTime`**](./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)).
//!
//! ```rust
//! use chrono::prelude::*;
//!
//! let utc: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
//! let local: DateTime<Local> = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00`
//! # 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.
//!
//! ```rust
//! use chrono::prelude::*;
//! use chrono::offset::LocalResult;
//!
//! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z`
//! // July 8 is 188th day of the year 2014 (`o` for "ordinal")
//! assert_eq!(dt, Utc.yo(2014, 189).and_hms_opt(9, 10, 11).unwrap());
//! // July 8 is Tuesday in ISO week 28 of the year 2014.
//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms_opt(9, 10, 11).unwrap());
//!
//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap().and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z`
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap().and_local_timezone(Utc).unwrap());
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap());
//!
//! // dynamic verification
//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33),
//! LocalResult::Single(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33).unwrap()));
//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None);
//! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None);
//!
//! // other time zone objects can be used to construct a local datetime.
//! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical.
//! let local_dt = Local.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;
//! ```
//!
//! 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
//! 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<FixedOffset>`, `DateTime<Utc>` and
//! `DateTime<Local>` values. This parses what the `{:?}`
//! ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html))
//! format specifier prints, and requires the offset to be present.
//!
//! 2. [`DateTime::parse_from_str`](./struct.DateTime.html#method.parse_from_str) parses
//! a date and time with offsets and returns `DateTime<FixedOffset>`.
//! This should be used when the offset is a part of input and the caller cannot guess that.
//! It *cannot* be used when the offset can be missing.
//! [`DateTime::parse_from_rfc2822`](./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::<DateTime<Utc>>(), Ok(dt.clone()));
//! assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<Utc>>(), Ok(dt.clone()));
//! assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<FixedOffset>>(), Ok(fixed_dt.clone()));
//!
//! // method 2
//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"),
//! Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"),
//! Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
//!
//! // method 3
//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone()));
//! assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone()));
//!
//! // oops, the year is missing!
//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err());
//! // oops, the format string does not include the year at all!
//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
//! // oops, the weekday is incorrect!
//! assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());
//! ```
//!
//! Again : See [`format::strftime`](./format/strftime/index.html#specifiers)
//! documentation for full syntax and list of specifiers.
//!
//! ### Conversion from and to EPOCH timestamps
//!
//! Use [`Utc.timestamp(seconds, nanoseconds)`](./offset/trait.TimeZone.html#method.timestamp)
//! to construct a [`DateTime<Utc>`](./struct.DateTime.html) from a UNIX timestamp
//! (seconds, nanoseconds that passed since January 1st 1970).
//!
//! Use [`DateTime.timestamp`](./struct.DateTime.html#method.timestamp) to get the timestamp (in seconds)
//! from a [`DateTime`](./struct.DateTime.html). Additionally, you can use
//! [`DateTime.timestamp_subsec_nanos`](./struct.DateTime.html#method.timestamp_subsec_nanos)
//! to get the number of additional number of nanoseconds.
//!
//! ```rust
//! // We need the trait in scope to use Utc::timestamp().
//! use chrono::{DateTime, TimeZone, Utc};
//!
//! // Construct a datetime from epoch:
//! let dt = Utc.timestamp(1_500_000_000, 0);
//! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
//!
//! // Get epoch value from a datetime:
//! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
//! assert_eq!(dt.timestamp(), 1_500_000_000);
//! ```
//!
//! ### Individual date
//!
//! Chrono also provides an individual date type ([**`Date`**](./struct.Date.html)).
//! It also has time zones attached, and have to be constructed via time zones.
//! Most operations available to `DateTime` are also available to `Date` whenever appropriate.
//!
//! ```rust
//! use chrono::prelude::*;
//! use chrono::offset::LocalResult;
//!
//! # // these *may* fail, but only very rarely. just rerun the test if you were that unfortunate ;)
//! assert_eq!(Utc::today(), Utc::now().date());
//! assert_eq!(Local::today(), Local::now().date());
//!
//! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().weekday(), Weekday::Fri);
//! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None);
//! assert_eq!(NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().and_local_timezone(Utc).unwrap().format("%H%M%S").to_string(),
//! "070809");
//! ```
//!
//! There is no timezone-aware `Time` due to the lack of usefulness and also the complexity.
//!
//! `DateTime` has [`date`](./struct.DateTime.html#method.date) method
//! which returns a `Date` which represents its date component.
//! There is also a [`time`](./struct.DateTime.html#method.time) method,
//! which simply returns a naive local time described below.
//!
//! ### Naive date and time
//!
//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime`
//! as [**`NaiveDate`**](./naive/struct.NaiveDate.html),
//! [**`NaiveTime`**](./naive/struct.NaiveTime.html) and
//! [**`NaiveDateTime`**](./naive/struct.NaiveDateTime.html) respectively.
//!
//! 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 proleptic Gregorian calendar (i.e. extended to support older dates) is supported.
//! Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others.
//!
//! Date types are limited in about +/- 262,000 years from the common epoch.
//! Time types are limited in the nanosecond accuracy.
//!
//! [Leap seconds are supported in the representation but
//! Chrono doesn't try to make use of them](./naive/struct.NaiveTime.html#leap-second-handling).
//! (The main reason is that leap seconds are not really predictable.)
//! Almost *every* operation over the possible leap seconds will ignore them.
//! Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale
//! if you want.
//!
//! Chrono inherently does not support an inaccurate or partial date and time representation.
//! Any operation that can be ambiguous will return `None` in such cases.
//! For example, "a month later" of 2014-01-30 is not well-defined
//! and consequently `Utc.ymd_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.
#![doc(html_root_url = "https://docs.rs/chrono/latest/")]
#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![warn(unreachable_pub)]
#![deny(dead_code)]
#![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 = "oldtime")]
#[cfg_attr(docsrs, doc(cfg(feature = "oldtime")))]
extern crate time as oldtime;
#[cfg(not(feature = "oldtime"))]
mod oldtime;
// this reexport is to aid the transition and should not be in the prelude!
pub use oldtime::{Duration, OutOfRangeError};
#[cfg(feature = "__doctest")]
#[cfg_attr(feature = "__doctest", cfg(doctest))]
use doc_comment::doctest;
#[cfg(feature = "__doctest")]
#[cfg_attr(feature = "__doctest", cfg(doctest))]
doctest!("../README.md");
/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`).
pub mod prelude {
#[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][1].
///
/// *Available on crate feature 'serde' only.*
///
/// [1]: https://tools.ietf.org/html/rfc3339
/// [2]: https://serde.rs/attributes.html#field-attributes
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde {
pub use super::datetime::serde::*;
}
/// MSRV 1.42
#[cfg(test)]
#[macro_export]
macro_rules! matches {
($expression:expr, $(|)? $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => {
match $expression {
$( $pattern )|+ $( if $guard )? => true,
_ => false
}
}
}

View File

@@ -0,0 +1,355 @@
use core::fmt;
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};
/// 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 num_traits::FromPrimitive;
/// 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::from_u32(date.month());
/// assert_eq!(month, Some(Month::October))
/// ```
/// Or from a Month to an integer usable by dates
/// ```
/// # use chrono::prelude::*;
/// let month = Month::January;
/// let dt = Utc.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)]
#[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]
pub fn succ(&self) -> Month {
match *self {
Month::January => Month::February,
Month::February => Month::March,
Month::March => Month::April,
Month::April => Month::May,
Month::May => Month::June,
Month::June => Month::July,
Month::July => Month::August,
Month::August => Month::September,
Month::September => Month::October,
Month::October => Month::November,
Month::November => Month::December,
Month::December => Month::January,
}
}
/// The previous month.
///
/// `m`: | `January` | `February` | `...` | `December`
/// ----------- | --------- | ---------- | --- | ---------
/// `m.pred()`: | `December` | `January` | `...` | `November`
#[inline]
pub fn pred(&self) -> Month {
match *self {
Month::January => Month::December,
Month::February => Month::January,
Month::March => Month::February,
Month::April => Month::March,
Month::May => Month::April,
Month::June => Month::May,
Month::July => Month::June,
Month::August => Month::July,
Month::September => Month::August,
Month::October => Month::September,
Month::November => Month::October,
Month::December => Month::November,
}
}
/// Returns a month-of-year number starting from January = 1.
///
/// `m`: | `January` | `February` | `...` | `December`
/// -------------------------| --------- | ---------- | --- | -----
/// `m.number_from_month()`: | 1 | 2 | `...` | 12
#[inline]
pub fn number_from_month(&self) -> u32 {
match *self {
Month::January => 1,
Month::February => 2,
Month::March => 3,
Month::April => 4,
Month::May => 5,
Month::June => 6,
Month::July => 7,
Month::August => 8,
Month::September => 9,
Month::October => 10,
Month::November => 11,
Month::December => 12,
}
}
/// Get the name of the month
///
/// ```
/// use chrono::Month;
///
/// assert_eq!(Month::January.name(), "January")
/// ```
pub fn name(&self) -> &'static str {
match *self {
Month::January => "January",
Month::February => "February",
Month::March => "March",
Month::April => "April",
Month::May => "May",
Month::June => "June",
Month::July => "July",
Month::August => "August",
Month::September => "September",
Month::October => "October",
Month::November => "November",
Month::December => "December",
}
}
}
impl num_traits::FromPrimitive for Month {
/// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1.
///
/// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12`
/// ---------------------------| -------------------- | --------------------- | ... | -----
/// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December)
#[inline]
fn from_u64(n: u64) -> Option<Month> {
Self::from_u32(n as u32)
}
#[inline]
fn from_i64(n: i64) -> Option<Month> {
Self::from_u32(n as u32)
}
#[inline]
fn from_u32(n: u32) -> Option<Month> {
match n {
1 => Some(Month::January),
2 => Some(Month::February),
3 => Some(Month::March),
4 => Some(Month::April),
5 => Some(Month::May),
6 => Some(Month::June),
7 => Some(Month::July),
8 => Some(Month::August),
9 => Some(Month::September),
10 => Some(Month::October),
11 => Some(Month::November),
12 => Some(Month::December),
_ => None,
}
}
}
/// A duration in calendar months
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
#[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 fn new(num: u32) -> Self {
Self(num)
}
}
/// An error resulting from reading `<Month>` value with `FromStr`.
#[derive(Clone, PartialEq, Eq)]
pub struct ParseMonthError {
pub(crate) _dummy: (),
}
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(self.name())
}
}
struct MonthVisitor;
impl<'de> de::Visitor<'de> for MonthVisitor {
type Value = Month;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Month")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected"))
}
}
impl<'de> de::Deserialize<'de> for Month {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(MonthVisitor)
}
}
#[test]
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]
fn test_serde_deserialize() {
use serde_json::from_str;
use Month::*;
let cases: Vec<(&str, Month)> = vec![
("\"january\"", January),
("\"jan\"", January),
("\"FeB\"", February),
("\"MAR\"", March),
("\"mar\"", March),
("\"april\"", April),
("\"may\"", May),
("\"june\"", June),
("\"JULY\"", July),
("\"august\"", August),
("\"september\"", September),
("\"October\"", October),
("\"November\"", November),
("\"DECEmbEr\"", December),
];
for (string, expected_month) in cases {
let month = from_str::<Month>(string).unwrap();
assert_eq!(month, expected_month);
}
let errors: Vec<&str> =
vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
for string in errors {
from_str::<Month>(string).unwrap_err();
}
}
}
#[cfg(test)]
mod tests {
use super::Month;
use crate::{Datelike, TimeZone, Utc};
#[test]
fn test_month_enum_primitive_parse() {
use num_traits::FromPrimitive;
let jan_opt = Month::from_u32(1);
let feb_opt = Month::from_u64(2);
let dec_opt = Month::from_i64(12);
let no_month = Month::from_u32(13);
assert_eq!(jan_opt, Some(Month::January));
assert_eq!(feb_opt, Some(Month::February));
assert_eq!(dec_opt, Some(Month::December));
assert_eq!(no_month, None);
let date = Utc.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);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
#![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<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
format!("{:?}", self).encode(s)
}
}
impl Decodable for NaiveDateTime {
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
d.read_str()?.parse().map_err(|_| d.error("invalid date time string"))
}
}
/// A `DateTime` that can be deserialized from a seconds-based timestamp
#[derive(Debug)]
#[deprecated(
since = "1.4.2",
note = "RustcSerialize will be removed before chrono 1.0, use Serde instead"
)]
pub struct TsSeconds(NaiveDateTime);
#[allow(deprecated)]
impl From<TsSeconds> for NaiveDateTime {
/// Pull the internal NaiveDateTime out
#[allow(deprecated)]
fn from(obj: TsSeconds) -> NaiveDateTime {
obj.0
}
}
#[allow(deprecated)]
impl Deref for TsSeconds {
type Target = NaiveDateTime;
#[allow(deprecated)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(deprecated)]
impl Decodable for TsSeconds {
#[allow(deprecated)]
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds, D::Error> {
Ok(TsSeconds(
NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0)
.ok_or_else(|| d.error("invalid timestamp"))?,
))
}
}
#[cfg(test)]
use rustc_serialize::json;
#[test]
fn test_encodable() {
super::test_encodable_json(json::encode);
}
#[test]
fn test_decodable() {
super::test_decodable_json(json::decode);
}
#[test]
fn test_decodable_timestamps() {
super::test_decodable_json_timestamp(json::decode);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
use super::NaiveDateTime;
use crate::oldtime::Duration;
use crate::NaiveDate;
use crate::{Datelike, FixedOffset, Utc};
use std::i64;
#[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());
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());
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: Duration,
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), Duration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10)));
check((2014, 5, 6, 7, 8, 9), Duration::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8)));
check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8)));
check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
// overflow check
// assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`.
// (they are private constants, but the equivalence is tested in that module.)
let max_days_from_year_0 =
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 + Duration::seconds(86399),
Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)),
);
check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None);
check((0, 1, 1, 0, 0, 0), Duration::max_value(), None);
let min_days_from_year_0 =
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 - Duration::seconds(1), None);
check((0, 1, 1, 0, 0, 0), Duration::min_value(), None);
}
#[test]
fn test_datetime_sub() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_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)), Duration::zero());
assert_eq!(
since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)),
Duration::seconds(1)
);
assert_eq!(
since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
Duration::seconds(-1)
);
assert_eq!(
since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
Duration::seconds(86399)
);
assert_eq!(
since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)),
Duration::seconds(999_999_999)
);
}
#[test]
fn test_datetime_addassignment() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let mut date = ymdhms(2016, 10, 1, 10, 10, 10);
date += Duration::minutes(10_000_000);
assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10));
date += Duration::days(10);
assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10));
}
#[test]
fn test_datetime_subassignment() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let mut date = ymdhms(2016, 10, 1, 10, 10, 10);
date -= Duration::minutes(10_000_000);
assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10));
date -= Duration::days(10);
assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10));
}
#[test]
fn test_datetime_timestamp() {
let to_timestamp = |y, m, d, h, n, s| {
NaiveDate::from_ymd_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 = [
"2015-2-18T23:16:9.15",
"-77-02-18T23:16:09",
" +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ",
];
for &s in &valid {
let d = match s.parse::<NaiveDateTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
let s_ = format!("{:?}", d);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveDateTime>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
);
}
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
assert!("".parse::<NaiveDateTime>().is_err());
assert!("x".parse::<NaiveDateTime>().is_err());
assert!("15".parse::<NaiveDateTime>().is_err());
assert!("15:8:9".parse::<NaiveDateTime>().is_err());
assert!("15-8-9".parse::<NaiveDateTime>().is_err());
assert!("2015-15-15T15:15:15".parse::<NaiveDateTime>().is_err());
assert!("2012-12-12T12:12:12x".parse::<NaiveDateTime>().is_err());
assert!("2012-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
assert!("+ 82701-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
assert!("+802701-123-12T12:12:12".parse::<NaiveDateTime>().is_err()); // out-of-bound
}
#[test]
fn test_datetime_parse_from_str() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_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_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]
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 + Duration::microseconds(t);
assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap());
}
#[test]
fn test_nanosecond_range() {
const A_BILLION: i64 = 1_000_000_000;
let maximum = "2262-04-11T23:47:16.854775804";
let parsed: NaiveDateTime = maximum.parse().unwrap();
let nanos = parsed.timestamp_nanos();
assert_eq!(
parsed,
NaiveDateTime::from_timestamp_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();
assert_eq!(
parsed,
NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap()
);
}
#[test]
fn test_and_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);
}

View File

@@ -0,0 +1,816 @@
// 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, i32};
use num_integer::{div_rem, mod_floor};
use num_traits::FromPrimitive;
/// The internal date representation. This also includes the packed `Mdf` value.
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)]
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);
static 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
];
static YEAR_DELTAS: [u8; 401] = [
0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14,
15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20,
21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100
25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30,
30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36,
36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42,
42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48,
48, 49, 49, 49, // 200
49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54,
54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60,
60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66,
66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72,
72, 73, 73, 73, // 300
73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78,
78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84,
84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90,
90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96,
96, 97, 97, 97, 97, // 400+1
];
pub(super) fn cycle_to_yo(cycle: u32) -> (u32, u32) {
let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365);
let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]);
if ordinal0 < delta {
year_mod_400 -= 1;
ordinal0 += 365 - u32::from(YEAR_DELTAS[year_mod_400 as usize]);
} else {
ordinal0 -= delta;
}
(year_mod_400, ordinal0 + 1)
}
pub(super) fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 {
year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1
}
impl YearFlags {
#[allow(unreachable_pub)] // public as an alias for benchmarks only
#[doc(hidden)] // for benchmarks only
#[inline]
pub fn from_year(year: i32) -> YearFlags {
let year = mod_floor(year, 400);
YearFlags::from_year_mod_400(year)
}
#[inline]
pub(super) fn from_year_mod_400(year: i32) -> YearFlags {
YEAR_TO_FLAGS[year as usize]
}
#[inline]
pub(super) fn ndays(&self) -> u32 {
let YearFlags(flags) = *self;
366 - u32::from(flags >> 3)
}
#[inline]
pub(super) fn isoweek_delta(&self) -> u32 {
let YearFlags(flags) = *self;
let mut delta = u32::from(flags) & 0b0111;
if delta < 3 {
delta += 7;
}
delta
}
#[inline]
pub(super) 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),
}
}
}
pub(super) const MIN_OL: u32 = 1 << 1;
pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1`
pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
const XX: i8 = -128;
static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [
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
];
static 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.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub(super) struct Of(pub(crate) u32);
impl Of {
#[inline]
pub(super) fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option<Of> {
match ordinal <= 366 {
true => Some(Of((ordinal << 4) | u32::from(flags))),
false => None,
}
}
#[inline]
pub(super) fn from_mdf(Mdf(mdf): Mdf) -> Of {
let mdl = mdf >> 3;
match MDL_TO_OL.get(mdl as usize) {
Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)),
None => Of(0),
}
}
#[inline]
pub(super) fn valid(&self) -> bool {
let Of(of) = *self;
let ol = of >> 3;
(MIN_OL..=MAX_OL).contains(&ol)
}
#[inline]
pub(super) fn ordinal(&self) -> u32 {
let Of(of) = *self;
of >> 4
}
#[inline]
pub(super) fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
if ordinal > 366 {
return None;
}
let Of(of) = *self;
Some(Of((of & 0b1111) | (ordinal << 4)))
}
#[inline]
pub(super) fn flags(&self) -> YearFlags {
let Of(of) = *self;
YearFlags((of & 0b1111) as u8)
}
#[inline]
pub(super) fn weekday(&self) -> Weekday {
let Of(of) = *self;
Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap()
}
#[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(weekord % 7).unwrap())
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
#[inline]
pub(super) fn to_mdf(&self) -> Mdf {
Mdf::from_of(*self)
}
#[inline]
pub(super) fn succ(&self) -> Of {
let Of(of) = *self;
Of(of + (1 << 4))
}
#[inline]
pub(super) fn pred(&self) -> Of {
let Of(of) = *self;
Of(of - (1 << 4))
}
}
impl fmt::Debug for Of {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Of(of) = *self;
write!(
f,
"Of(({} << 4) | {:#04o} /*{:?}*/)",
of >> 4,
of & 0b1111,
YearFlags((of & 0b1111) as u8)
)
}
}
/// Month, day of month and year flags: `(month << 9) | (day << 4) | flags`
///
/// 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.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub(super) struct Mdf(pub(super) u32);
impl Mdf {
#[inline]
pub(super) fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
match month <= 12 && day <= 31 {
true => Some(Mdf((month << 9) | (day << 4) | u32::from(flags))),
false => None,
}
}
#[inline]
pub(super) fn from_of(Of(of): Of) -> Mdf {
let ol = of >> 3;
match OL_TO_MDL.get(ol as usize) {
Some(&v) => Mdf(of + (u32::from(v) << 3)),
None => Mdf(0),
}
}
#[cfg(test)]
pub(super) fn valid(&self) -> bool {
let Mdf(mdf) = *self;
let mdl = mdf >> 3;
match MDL_TO_OL.get(mdl as usize) {
Some(&v) => v >= 0,
None => false,
}
}
#[inline]
pub(super) fn month(&self) -> u32 {
let Mdf(mdf) = *self;
mdf >> 9
}
#[inline]
pub(super) fn with_month(&self, month: u32) -> Option<Mdf> {
if month > 12 {
return None;
}
let Mdf(mdf) = *self;
Some(Mdf((mdf & 0b1_1111_1111) | (month << 9)))
}
#[inline]
pub(super) fn day(&self) -> u32 {
let Mdf(mdf) = *self;
(mdf >> 4) & 0b1_1111
}
#[inline]
pub(super) fn with_day(&self, day: u32) -> Option<Mdf> {
if day > 31 {
return None;
}
let Mdf(mdf) = *self;
Some(Mdf((mdf & !0b1_1111_0000) | (day << 4)))
}
#[inline]
pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
let Mdf(mdf) = *self;
Mdf((mdf & !0b1111) | u32::from(flags))
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
#[inline]
pub(super) fn to_of(&self) -> Of {
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)
)
}
}
#[cfg(test)]
mod tests {
use num_iter::range_inclusive;
use std::u32;
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 range_inclusive(ordinal1, ordinal2) {
let of = match Of::new(ordinal, flags) {
Some(of) => of,
None if !expected => continue,
None => panic!("Of::new({}, {:?}) returned None", ordinal, flags),
};
assert!(
of.valid() == expected,
"ordinal {} = {:?} should be {} for dominical year {:?}",
ordinal,
of,
if expected { "valid" } else { "invalid" },
flags
);
}
}
for &flags in NONLEAP_FLAGS.iter() {
check(false, flags, 0, 0);
check(true, flags, 1, 365);
check(false, flags, 366, 1024);
check(false, flags, u32::MAX, u32::MAX);
}
for &flags in LEAP_FLAGS.iter() {
check(false, flags, 0, 0);
check(true, flags, 1, 366);
check(false, flags, 367, 1024);
check(false, flags, u32::MAX, u32::MAX);
}
}
#[test]
fn test_mdf_valid() {
fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
for month in range_inclusive(month1, month2) {
for day in range_inclusive(day1, day2) {
let mdf = 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 range_inclusive(1u32, 366) {
let of = Of::new(ordinal, flags).unwrap();
if of.valid() {
assert_eq!(of.ordinal(), ordinal);
}
}
}
}
#[test]
fn test_of_with_fields() {
fn check(flags: YearFlags, ordinal: u32) {
let of = Of::new(ordinal, flags).unwrap();
for ordinal in range_inclusive(0u32, 1024) {
let of = match of.with_ordinal(ordinal) {
Some(of) => of,
None if ordinal > 366 => continue,
None => panic!("failed to create Of with ordinal {}", ordinal),
};
assert_eq!(of.valid(), Of::new(ordinal, flags).unwrap().valid());
if of.valid() {
assert_eq!(of.ordinal(), ordinal);
}
}
}
for &flags in NONLEAP_FLAGS.iter() {
check(flags, 1);
check(flags, 365);
}
for &flags in LEAP_FLAGS.iter() {
check(flags, 1);
check(flags, 366);
}
}
#[test]
fn test_of_weekday() {
assert_eq!(Of::new(1, A).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 range_inclusive(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 range_inclusive(1u32, 12) {
for day in range_inclusive(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 range_inclusive(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 range_inclusive(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 range_inclusive(0u32, 8192) {
let of = Of(i);
assert_eq!(of.valid(), of.to_mdf().valid());
}
}
#[test]
fn test_mdf_to_of() {
for i in range_inclusive(0u32, 8192) {
let mdf = Mdf(i);
assert_eq!(mdf.valid(), mdf.to_of().valid());
}
}
#[test]
fn test_of_to_mdf_to_of() {
for i in range_inclusive(0u32, 8192) {
let of = Of(i);
if of.valid() {
assert_eq!(of, of.to_mdf().to_of());
}
}
}
#[test]
fn test_mdf_to_of_to_mdf() {
for i in range_inclusive(0u32, 8192) {
let mdf = Mdf(i);
if mdf.valid() {
assert_eq!(mdf, mdf.to_of().to_mdf());
}
}
}
}

View File

@@ -0,0 +1,167 @@
// 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)]
#[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)
}
};
IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) }
}
impl IsoWeek {
/// Returns the year number for this ISO week.
///
/// # Example
///
/// ```
/// use chrono::{NaiveDate, Datelike, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// assert_eq!(d.iso_week().year(), 2015);
/// ```
///
/// This year number might not match the calendar year number.
/// Continuing the example...
///
/// ```
/// # use chrono::{NaiveDate, Datelike, Weekday};
/// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// assert_eq!(d.year(), 2014);
/// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap());
/// ```
#[inline]
pub 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(2015, 15, Weekday::Mon);
/// assert_eq!(d.iso_week().week(), 15);
/// ```
#[inline]
pub 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(2015, 15, Weekday::Mon);
/// assert_eq!(d.iso_week().week0(), 14);
/// ```
#[inline]
pub 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);
assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string());
assert_eq!(maxweek.year(), internals::MAX_YEAR + 1);
assert_eq!(maxweek.week(), 1);
assert_eq!(maxweek.week0(), 0);
assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string());
}
}

View File

@@ -0,0 +1,39 @@
//! Date and time types unconcerned with timezones.
//!
//! They are primarily building blocks for other types
//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)),
//! but can be also used for the simpler date and time handling.
mod date;
pub(crate) mod datetime;
mod internals;
mod isoweek;
mod time;
#[allow(deprecated)]
pub use self::date::{Days, NaiveDate, NaiveWeek, 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::*;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))]
use super::NaiveTime;
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
impl Encodable for NaiveTime {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
format!("{:?}", self).encode(s)
}
}
impl Decodable for NaiveTime {
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveTime, D::Error> {
d.read_str()?.parse().map_err(|_| d.error("invalid time"))
}
}
#[cfg(test)]
use rustc_serialize::json;
#[test]
fn test_encodable() {
super::test_encodable_json(json::encode);
}
#[test]
fn test_decodable() {
super::test_decodable_json(json::decode);
}

View File

@@ -0,0 +1,65 @@
#![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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(&self)
}
}
struct NaiveTimeVisitor;
impl<'de> de::Visitor<'de> for NaiveTimeVisitor {
type Value = NaiveTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a formatted time string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(E::custom)
}
}
impl<'de> de::Deserialize<'de> for NaiveTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(NaiveTimeVisitor)
}
}
#[test]
fn test_serde_serialize() {
super::test_encodable_json(serde_json::to_string);
}
#[test]
fn test_serde_deserialize() {
super::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);
}

View File

@@ -0,0 +1,317 @@
use super::NaiveTime;
use crate::oldtime::Duration;
use crate::Timelike;
use std::u32;
#[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, 7, 1_999),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_000_000).unwrap())
);
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None);
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None);
}
#[test]
fn test_time_from_hms_micro() {
assert_eq!(
NaiveTime::from_hms_micro_opt(3, 5, 7, 0),
Some(NaiveTime::from_hms_nano_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, 7, 1_999_999),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_999_000).unwrap())
);
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None);
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None);
}
#[test]
fn test_time_hms() {
assert_eq!(NaiveTime::from_hms_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, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900));
check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100));
check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100));
check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap
check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900));
check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900));
check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300));
check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300));
// regression tests for #37
check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10));
check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10));
}
#[test]
fn test_time_overflowing_add() {
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)),
(hmsm(14, 4, 5, 678), 0)
);
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)),
(hmsm(2, 4, 5, 678), 86_400)
);
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)),
(hmsm(20, 4, 5, 678), -86_400)
);
// overflowing_add_signed with leap seconds may be counter-intuitive
assert_eq!(
hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)),
(hmsm(3, 4, 5, 678), 86_400)
);
assert_eq!(
hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)),
(hmsm(3, 4, 6, 678), -86_400)
);
}
#[test]
fn test_time_addassignment() {
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
let mut time = hms(12, 12, 12);
time += Duration::hours(10);
assert_eq!(time, hms(22, 12, 12));
time += Duration::hours(10);
assert_eq!(time, hms(8, 12, 12));
}
#[test]
fn test_time_subassignment() {
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
let mut time = hms(12, 12, 12);
time -= Duration::hours(10);
assert_eq!(time, hms(2, 12, 12));
time -= Duration::hours(10);
assert_eq!(time, hms(16, 12, 12));
}
#[test]
fn test_time_sub() {
macro_rules! check {
($lhs:expr, $rhs:expr, $diff:expr) => {{
// `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration`
assert_eq!($lhs.signed_duration_since($rhs), $diff);
assert_eq!($rhs.signed_duration_since($lhs), -$diff);
}};
}
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero());
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300));
check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1));
check!(
hmsm(3, 5, 7, 200),
hmsm(2, 4, 6, 300),
Duration::seconds(3600 + 60) + Duration::milliseconds(900)
);
// treats the leap second as if it coincides with the prior non-leap second,
// as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence.
check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400));
check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400));
check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400));
// additional equality: `time1 + duration = time2` is equivalent to
// `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second.
assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200));
assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200));
}
#[test]
fn test_time_fmt() {
assert_eq!(
format!("{}", NaiveTime::from_hms_milli_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_date_from_str() {
// valid cases
let valid = [
"0:0:0",
"0:0:0.0000000",
"0:0:0.0000003",
" 4 : 3 : 2.1 ",
" 09:08:07 ",
" 9:8:07 ",
"23:59:60.373929310237",
];
for &s in &valid {
let d = match s.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
let s_ = format!("{:?}", d);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
);
}
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
assert!("".parse::<NaiveTime>().is_err());
assert!("x".parse::<NaiveTime>().is_err());
assert!("15".parse::<NaiveTime>().is_err());
assert!("15:8".parse::<NaiveTime>().is_err());
assert!("15:8:x".parse::<NaiveTime>().is_err());
assert!("15:8:9x".parse::<NaiveTime>().is_err());
assert!("23:59:61".parse::<NaiveTime>().is_err());
assert!("12:34:56.x".parse::<NaiveTime>().is_err());
assert!("12:34:56. 0".parse::<NaiveTime>().is_err());
}
#[test]
fn test_time_parse_from_str() {
let hms = |h, m, s| NaiveTime::from_hms_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!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err());
}
#[test]
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"
);
}

View File

@@ -0,0 +1,284 @@
// 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 num_integer::div_mod_floor;
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};
use super::{LocalResult, Offset, TimeZone};
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
use crate::oldtime::Duration as OldDuration;
use crate::DateTime;
use crate::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<FixedOffset>` instances. See the [`east`](#method.east) and
/// [`west`](#method.west) 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")]
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
///
/// ```
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime = FixedOffset::east_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap()
/// .and_hms_opt(0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
/// ```
pub fn east_opt(secs: i32) -> Option<FixedOffset> {
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")]
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
///
/// ```
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime = FixedOffset::west_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap()
/// .and_hms_opt(0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
/// ```
pub fn west_opt(secs: i32) -> Option<FixedOffset> {
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 fn local_minus_utc(&self) -> i32 {
self.local_minus_utc
}
/// Returns the number of seconds to add to convert from the local time to UTC.
#[inline]
pub fn utc_minus_local(&self) -> i32 {
-self.local_minus_utc
}
}
impl TimeZone for FixedOffset {
type Offset = FixedOffset;
fn from_offset(offset: &FixedOffset) -> FixedOffset {
*offset
}
fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
LocalResult::Single(*self)
}
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
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 (mins, sec) = div_mod_floor(offset, 60);
let (hour, min) = div_mod_floor(mins, 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<FixedOffset> {
let secs = u.int_in_range(-86_399..=86_399)?;
let fixed_offset = FixedOffset::east_opt(secs)
.expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous.");
Ok(fixed_offset)
}
}
// addition or subtraction of FixedOffset to/from Timelike values is the same as
// adding or subtracting the offset's local_minus_utc value
// but keep keeps the leap second information.
// this should be implemented more efficiently, but for the time being, this is generic right now.
fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
where
T: Timelike + Add<OldDuration, Output = T>,
{
// extract and temporarily remove the fractional part and later recover it
let nanos = lhs.nanosecond();
let lhs = lhs.with_nanosecond(0).unwrap();
(lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
}
impl Add<FixedOffset> for NaiveTime {
type Output = NaiveTime;
#[inline]
fn add(self, rhs: FixedOffset) -> NaiveTime {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl Sub<FixedOffset> for NaiveTime {
type Output = NaiveTime;
#[inline]
fn sub(self, rhs: FixedOffset) -> NaiveTime {
add_with_leapsecond(&self, -rhs.local_minus_utc)
}
}
impl Add<FixedOffset> for NaiveDateTime {
type Output = NaiveDateTime;
#[inline]
fn add(self, rhs: FixedOffset) -> NaiveDateTime {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl Sub<FixedOffset> for NaiveDateTime {
type Output = NaiveDateTime;
#[inline]
fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
add_with_leapsecond(&self, -rhs.local_minus_utc)
}
}
impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
type Output = DateTime<Tz>;
#[inline]
fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
type Output = DateTime<Tz>;
#[inline]
fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
add_with_leapsecond(&self, -rhs.local_minus_utc)
}
}
#[cfg(test)]
mod tests {
use super::FixedOffset;
use crate::offset::TimeZone;
#[test]
fn test_date_extreme_offset() {
// starting from 0.3 we don't have an offset exceeding one day.
// this makes everything easier!
assert_eq!(
format!(
"{:?}",
FixedOffset::east_opt(86399)
.unwrap()
.with_ymd_and_hms(2012, 2, 29, 5, 6, 7)
.unwrap()
),
"2012-02-29T05:06:07+23:59:59".to_string()
);
assert_eq!(
format!(
"{:?}",
FixedOffset::east_opt(86399)
.unwrap()
.with_ymd_and_hms(2012, 2, 29, 5, 6, 7)
.unwrap()
),
"2012-02-29T05:06:07+23:59:59".to_string()
);
assert_eq!(
format!(
"{:?}",
FixedOffset::west_opt(86399)
.unwrap()
.with_ymd_and_hms(2012, 3, 4, 5, 6, 7)
.unwrap()
),
"2012-03-04T05:06:07-23:59:59".to_string()
);
assert_eq!(
format!(
"{:?}",
FixedOffset::west_opt(86399)
.unwrap()
.with_ymd_and_hms(2012, 3, 4, 5, 6, 7)
.unwrap()
),
"2012-03-04T05:06:07-23:59:59".to_string()
);
}
}

View File

@@ -0,0 +1,260 @@
// 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};
#[allow(deprecated)]
use crate::{Date, DateTime};
// we don't want `stub.rs` when the target_os is not wasi or emscripten
// as we use js-sys to get the date instead
#[cfg(all(
not(unix),
not(windows),
not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))
))]
#[path = "stub.rs"]
mod inner;
#[cfg(unix)]
#[path = "unix.rs"]
mod inner;
#[cfg(windows)]
#[path = "windows.rs"]
mod inner;
#[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<Local>`
/// instances.
///
/// # Example
///
/// ```
/// use chrono::{Local, DateTime, TimeZone};
///
/// let dt: DateTime<Local> = Local::now();
/// let dt: DateTime<Local> = Local.timestamp(0, 0);
/// ```
#[derive(Copy, Clone, Debug)]
#[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)]
pub fn today() -> Date<Local> {
Local::now().date()
}
/// Returns a `DateTime` which corresponds to the current date and time.
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
pub fn now() -> DateTime<Local> {
inner::now()
}
/// 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"))
))]
pub fn now() -> DateTime<Local> {
use super::Utc;
let now: DateTime<Utc> = super::Utc::now();
// Workaround missing timezone logic in `time` crate
let offset =
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
.unwrap();
DateTime::from_utc(now.naive_utc(), offset)
}
}
impl TimeZone for Local {
type Offset = FixedOffset;
fn from_offset(_offset: &FixedOffset) -> Local {
Local
}
// they are easier to define in terms of the finished date and time unlike other offsets
#[allow(deprecated)]
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<FixedOffset> {
self.from_local_date(local).map(|date| *date.offset())
}
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
self.from_local_datetime(local).map(|datetime| *datetime.offset())
}
#[allow(deprecated)]
fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
*self.from_utc_date(utc).offset()
}
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
*self.from_utc_datetime(utc).offset()
}
// override them for avoiding redundant works
#[allow(deprecated)]
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Local>> {
// this sounds very strange, but required for keeping `TimeZone::ymd` sane.
// in the other words, we use the offset at the local midnight
// but keep the actual date unaltered (much like `FixedOffset`).
let midnight = self.from_local_datetime(&local.and_hms_opt(0, 0, 0).unwrap());
midnight.map(|datetime| Date::from_utc(*local, *datetime.offset()))
}
#[cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let mut local = local.clone();
// Get the offset from the js runtime
let offset =
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
.unwrap();
local -= crate::Duration::seconds(offset.local_minus_utc() as i64);
LocalResult::Single(DateTime::from_utc(local, offset))
}
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
inner::naive_to_local(local, true)
}
#[allow(deprecated)]
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap());
Date::from_utc(*utc, *midnight.offset())
}
#[cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
// Get the offset from the js runtime
let offset =
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
.unwrap();
DateTime::from_utc(*utc, offset)
}
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
// this is OK to unwrap as getting local time from a UTC
// timestamp is never ambiguous
inner::naive_to_local(utc, false).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();
let dt = today.and_hms_milli_opt(1, 2, 59, 1000).unwrap();
let timestr = dt.time().to_string();
// the OS API may or may not support the leap second,
// but there are only two sensible options.
assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr);
let dt = today.and_hms_milli_opt(1, 2, 3, 1234).unwrap();
let timestr = dt.time().to_string();
assert!(
timestr == "01:02:03.234" || timestr == "01:02:04.234",
"unexpected timestr {:?}",
timestr
);
}
}

View File

@@ -0,0 +1,237 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::time::{SystemTime, UNIX_EPOCH};
use super::{FixedOffset, Local};
use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
pub(super) fn now() -> DateTime<Local> {
tm_to_datetime(Timespec::now().local())
}
/// Converts a local `NaiveDateTime` to the `time::Timespec`.
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
let tm = Tm {
tm_sec: d.second() as i32,
tm_min: d.minute() as i32,
tm_hour: d.hour() as i32,
tm_mday: d.day() as i32,
tm_mon: d.month0() as i32, // yes, C is that strange...
tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`.
tm_wday: 0, // to_local ignores this
tm_yday: 0, // and this
tm_isdst: -1,
// This seems pretty fake?
tm_utcoff: if local { 1 } else { 0 },
// do not set this, OS APIs are heavily inconsistent in terms of leap second handling
tm_nsec: 0,
};
let spec = Timespec {
sec: match local {
false => utc_tm_to_time(&tm),
true => local_tm_to_time(&tm),
},
nsec: tm.tm_nsec,
};
// Adjust for leap seconds
let mut tm = spec.local();
assert_eq!(tm.tm_nsec, 0);
tm.tm_nsec = d.nanosecond() as i32;
LocalResult::Single(tm_to_datetime(tm))
}
/// Converts a `time::Tm` struct into the timezone-aware `DateTime`.
/// This assumes that `time` is working correctly, i.e. any error is fatal.
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
fn tm_to_datetime(mut tm: Tm) -> DateTime<Local> {
if tm.tm_sec >= 60 {
tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000;
tm.tm_sec = 59;
}
let date = NaiveDate::from_yo_opt(tm.tm_year + 1900, tm.tm_yday as u32 + 1)
.expect("invalid or out-of-range date");
let time = NaiveTime::from_hms_nano_opt(
tm.tm_hour as u32,
tm.tm_min as u32,
tm.tm_sec as u32,
tm.tm_nsec as u32,
).expect("invalid time");
let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap();
DateTime::from_utc(date.and_time(time) - offset, offset)
}
/// A record specifying a time value in seconds and nanoseconds, where
/// nanoseconds represent the offset from the given second.
///
/// For example a timespec of 1.2 seconds after the beginning of the epoch would
/// be represented as {sec: 1, nsec: 200000000}.
struct Timespec {
sec: i64,
nsec: i32,
}
impl Timespec {
/// Constructs a timespec representing the current time in UTC.
fn now() -> Timespec {
let st =
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 }
}
/// Converts this timespec into the system's local time.
fn local(self) -> Tm {
let mut tm = Tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_utcoff: 0,
tm_nsec: 0,
};
time_to_local_tm(self.sec, &mut tm);
tm.tm_nsec = self.nsec;
tm
}
}
/// Holds a calendar date and time broken down into its components (year, month,
/// day, and so on), also called a broken-down time value.
// FIXME: use c_int instead of i32?
#[repr(C)]
pub(super) struct Tm {
/// Seconds after the minute - [0, 60]
tm_sec: i32,
/// Minutes after the hour - [0, 59]
tm_min: i32,
/// Hours after midnight - [0, 23]
tm_hour: i32,
/// Day of the month - [1, 31]
tm_mday: i32,
/// Months since January - [0, 11]
tm_mon: i32,
/// Years since 1900
tm_year: i32,
/// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
tm_wday: i32,
/// Days since January 1 - [0, 365]
tm_yday: i32,
/// Daylight Saving Time flag.
///
/// This value is positive if Daylight Saving Time is in effect, zero if
/// Daylight Saving Time is not in effect, and negative if this information
/// is not available.
tm_isdst: i32,
/// Identifies the time zone that was used to compute this broken-down time
/// value, including any adjustment for Daylight Saving Time. This is the
/// number of seconds east of UTC. For example, for U.S. Pacific Daylight
/// Time, the value is `-7*60*60 = -25200`.
tm_utcoff: i32,
/// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
tm_nsec: i32,
}
fn time_to_tm(ts: i64, tm: &mut Tm) {
let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) };
static YTAB: [[i64; 12]; 2] = [
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
];
let mut year = 1970;
let dayclock = ts % 86400;
let mut dayno = ts / 86400;
tm.tm_sec = (dayclock % 60) as i32;
tm.tm_min = ((dayclock % 3600) / 60) as i32;
tm.tm_hour = (dayclock / 3600) as i32;
tm.tm_wday = ((dayno + 4) % 7) as i32;
loop {
let yearsize = if leapyear(year) { 366 } else { 365 };
if dayno >= yearsize {
dayno -= yearsize;
year += 1;
} else {
break;
}
}
tm.tm_year = (year - 1900) as i32;
tm.tm_yday = dayno as i32;
let mut mon = 0;
while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] {
dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon];
mon += 1;
}
tm.tm_mon = mon as i32;
tm.tm_mday = dayno as i32 + 1;
tm.tm_isdst = 0;
}
fn tm_to_time(tm: &Tm) -> i64 {
let mut y = tm.tm_year as i64 + 1900;
let mut m = tm.tm_mon as i64 + 1;
if m <= 2 {
y -= 1;
m += 12;
}
let d = tm.tm_mday as i64;
let h = tm.tm_hour as i64;
let mi = tm.tm_min as i64;
let s = tm.tm_sec as i64;
(365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400
+ 3600 * h
+ 60 * mi
+ s
}
pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) {
// FIXME: Add timezone logic
time_to_tm(sec, tm);
}
pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 {
tm_to_time(tm)
}
pub(super) fn local_tm_to_time(tm: &Tm) -> i64 {
// FIXME: Add timezone logic
tm_to_time(tm)
}

View File

@@ -0,0 +1,131 @@
#![deny(missing_docs)]
#![allow(dead_code)]
#![warn(unreachable_pub)]
use std::num::ParseIntError;
use std::str::Utf8Error;
use std::time::SystemTimeError;
use std::{error, fmt, io};
mod timezone;
pub(crate) use timezone::TimeZone;
mod parser;
mod rule;
/// Unified error type for everything in the crate
#[derive(Debug)]
pub(crate) enum Error {
/// Date time error
DateTime(&'static str),
/// Local time type search error
FindLocalTimeType(&'static str),
/// Local time type error
LocalTimeType(&'static str),
/// Invalid slice for integer conversion
InvalidSlice(&'static str),
/// Invalid Tzif file
InvalidTzFile(&'static str),
/// Invalid TZ string
InvalidTzString(&'static str),
/// I/O error
Io(io::Error),
/// Out of range error
OutOfRange(&'static str),
/// Integer parsing error
ParseInt(ParseIntError),
/// Date time projection error
ProjectDateTime(&'static str),
/// System time error
SystemTime(SystemTimeError),
/// Time zone error
TimeZone(&'static str),
/// Transition rule error
TransitionRule(&'static str),
/// Unsupported Tzif file
UnsupportedTzFile(&'static str),
/// Unsupported TZ string
UnsupportedTzString(&'static str),
/// UTF-8 error
Utf8(Utf8Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
DateTime(error) => write!(f, "invalid date time: {}", error),
FindLocalTimeType(error) => error.fmt(f),
LocalTimeType(error) => write!(f, "invalid local time type: {}", error),
InvalidSlice(error) => error.fmt(f),
InvalidTzString(error) => write!(f, "invalid TZ string: {}", error),
InvalidTzFile(error) => error.fmt(f),
Io(error) => error.fmt(f),
OutOfRange(error) => error.fmt(f),
ParseInt(error) => error.fmt(f),
ProjectDateTime(error) => error.fmt(f),
SystemTime(error) => error.fmt(f),
TransitionRule(error) => write!(f, "invalid transition rule: {}", error),
TimeZone(error) => write!(f, "invalid time zone: {}", error),
UnsupportedTzFile(error) => error.fmt(f),
UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error),
Utf8(error) => error.fmt(f),
}
}
}
impl error::Error for Error {}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::Io(error)
}
}
impl From<ParseIntError> for Error {
fn from(error: ParseIntError) -> Self {
Error::ParseInt(error)
}
}
impl From<SystemTimeError> for Error {
fn from(error: SystemTimeError) -> Self {
Error::SystemTime(error)
}
}
impl From<Utf8Error> for Error {
fn from(error: Utf8Error) -> Self {
Error::Utf8(error)
}
}
// MSRV: 1.38
#[inline]
fn rem_euclid(v: i64, rhs: i64) -> i64 {
let r = v % rhs;
if r < 0 {
if rhs < 0 {
r - rhs
} else {
r + rhs
}
} else {
r
}
}
/// 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];

View File

@@ -0,0 +1,334 @@
use std::io::{self, ErrorKind};
use std::iter;
use std::num::ParseIntError;
use std::str::{self, FromStr};
use super::rule::TransitionRule;
use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition};
use super::Error;
#[allow(clippy::map_clone)] // MSRV: 1.36
pub(super) fn parse(bytes: &[u8]) -> Result<TimeZone, Error> {
let mut cursor = Cursor::new(bytes);
let state = State::new(&mut cursor, true)?;
let (state, footer) = match state.header.version {
Version::V1 => match cursor.is_empty() {
true => (state, None),
false => {
return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block"))
}
},
Version::V2 | Version::V3 => {
let state = State::new(&mut cursor, false)?;
(state, Some(cursor.remaining()))
}
};
let mut transitions = Vec::with_capacity(state.header.transition_count);
for (arr_time, &local_time_type_index) in
state.transition_times.chunks_exact(state.time_size).zip(state.transition_types)
{
let unix_leap_time =
state.parse_time(&arr_time[0..state.time_size], state.header.version)?;
let local_time_type_index = local_time_type_index as usize;
transitions.push(Transition::new(unix_leap_time, local_time_type_index));
}
let mut local_time_types = Vec::with_capacity(state.header.type_count);
for arr in state.local_time_types.chunks_exact(6) {
let ut_offset = read_be_i32(&arr[..4])?;
let is_dst = match arr[4] {
0 => false,
1 => true,
_ => return Err(Error::InvalidTzFile("invalid DST indicator")),
};
let char_index = arr[5] as usize;
if char_index >= state.header.char_count {
return Err(Error::InvalidTzFile("invalid time zone name char index"));
}
let position = match state.names[char_index..].iter().position(|&c| c == b'\0') {
Some(position) => position,
None => return Err(Error::InvalidTzFile("invalid time zone name char index")),
};
let name = &state.names[char_index..char_index + position];
let name = if !name.is_empty() { Some(name) } else { None };
local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?);
}
let mut leap_seconds = Vec::with_capacity(state.header.leap_count);
for arr in state.leap_seconds.chunks_exact(state.time_size + 4) {
let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?;
let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?;
leap_seconds.push(LeapSecond::new(unix_leap_time, correction));
}
let std_walls_iter = state.std_walls.iter().map(|&i| i).chain(iter::repeat(0));
let ut_locals_iter = state.ut_locals.iter().map(|&i| i).chain(iter::repeat(0));
if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) {
return Err(Error::InvalidTzFile(
"invalid couple of standard/wall and UT/local indicators",
));
}
let extra_rule = match footer {
Some(footer) => {
let footer = str::from_utf8(footer)?;
if !(footer.starts_with('\n') && footer.ends_with('\n')) {
return Err(Error::InvalidTzFile("invalid footer"));
}
let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace());
if tz_string.starts_with(':') || tz_string.contains('\0') {
return Err(Error::InvalidTzFile("invalid footer"));
}
match tz_string.is_empty() {
true => None,
false => Some(TransitionRule::from_tz_string(
tz_string.as_bytes(),
state.header.version == Version::V3,
)?),
}
}
None => None,
};
TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule)
}
/// TZif data blocks
struct State<'a> {
header: Header,
/// Time size in bytes
time_size: usize,
/// Transition times data block
transition_times: &'a [u8],
/// Transition types data block
transition_types: &'a [u8],
/// Local time types data block
local_time_types: &'a [u8],
/// Time zone names data block
names: &'a [u8],
/// Leap seconds data block
leap_seconds: &'a [u8],
/// UT/local indicators data block
std_walls: &'a [u8],
/// Standard/wall indicators data block
ut_locals: &'a [u8],
}
impl<'a> State<'a> {
/// Read TZif data blocks
fn new(cursor: &mut Cursor<'a>, first: bool) -> Result<Self, Error> {
let header = Header::new(cursor)?;
let time_size = match first {
true => 4, // We always parse V1 first
false => 8,
};
Ok(Self {
time_size,
transition_times: cursor.read_exact(header.transition_count * time_size)?,
transition_types: cursor.read_exact(header.transition_count)?,
local_time_types: cursor.read_exact(header.type_count * 6)?,
names: cursor.read_exact(header.char_count)?,
leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?,
std_walls: cursor.read_exact(header.std_wall_count)?,
ut_locals: cursor.read_exact(header.ut_local_count)?,
header,
})
}
/// Parse time values
fn parse_time(&self, arr: &[u8], version: Version) -> Result<i64, Error> {
match version {
Version::V1 => Ok(read_be_i32(&arr[..4])?.into()),
Version::V2 | Version::V3 => read_be_i64(arr),
}
}
}
/// TZif header
#[derive(Debug)]
struct Header {
/// TZif version
version: Version,
/// Number of UT/local indicators
ut_local_count: usize,
/// Number of standard/wall indicators
std_wall_count: usize,
/// Number of leap-second records
leap_count: usize,
/// Number of transition times
transition_count: usize,
/// Number of local time type records
type_count: usize,
/// Number of time zone names bytes
char_count: usize,
}
impl Header {
fn new(cursor: &mut Cursor) -> Result<Self, Error> {
let magic = cursor.read_exact(4)?;
if magic != *b"TZif" {
return Err(Error::InvalidTzFile("invalid magic number"));
}
let version = match cursor.read_exact(1)? {
[0x00] => Version::V1,
[0x32] => Version::V2,
[0x33] => Version::V3,
_ => return Err(Error::UnsupportedTzFile("unsupported TZif version")),
};
cursor.read_exact(15)?;
let ut_local_count = cursor.read_be_u32()?;
let std_wall_count = cursor.read_be_u32()?;
let leap_count = cursor.read_be_u32()?;
let transition_count = cursor.read_be_u32()?;
let type_count = cursor.read_be_u32()?;
let char_count = cursor.read_be_u32()?;
if !(type_count != 0
&& char_count != 0
&& (ut_local_count == 0 || ut_local_count == type_count)
&& (std_wall_count == 0 || std_wall_count == type_count))
{
return Err(Error::InvalidTzFile("invalid header"));
}
Ok(Self {
version,
ut_local_count: ut_local_count as usize,
std_wall_count: std_wall_count as usize,
leap_count: leap_count as usize,
transition_count: transition_count as usize,
type_count: type_count as usize,
char_count: char_count as usize,
})
}
}
/// A `Cursor` contains a slice of a buffer and a read count.
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Cursor<'a> {
/// Slice representing the remaining data to be read
remaining: &'a [u8],
/// Number of already read bytes
read_count: usize,
}
impl<'a> Cursor<'a> {
/// Construct a new `Cursor` from remaining data
pub(crate) 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) fn remaining(&self) -> &'a [u8] {
self.remaining
}
/// Returns `true` if data is remaining
pub(crate) fn is_empty(&self) -> bool {
self.remaining.is_empty()
}
pub(crate) fn read_be_u32(&mut self) -> Result<u32, Error> {
let mut buf = [0; 4];
buf.copy_from_slice(self.read_exact(4)?);
Ok(u32::from_be_bytes(buf))
}
/// Read exactly `count` bytes, reducing remaining data and incrementing read count
pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> {
match (self.remaining.get(..count), self.remaining.get(count..)) {
(Some(result), Some(remaining)) => {
self.remaining = remaining;
self.read_count += count;
Ok(result)
}
_ => Err(io::Error::from(ErrorKind::UnexpectedEof)),
}
}
/// Read bytes and compare them to the provided tag
pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> {
if self.read_exact(tag.len())? == tag {
Ok(())
} else {
Err(io::Error::from(ErrorKind::InvalidData))
}
}
/// Read bytes if the remaining data is prefixed by the provided tag
pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result<bool, io::Error> {
if self.remaining.starts_with(tag) {
self.read_exact(tag.len())?;
Ok(true)
} else {
Ok(false)
}
}
/// Read bytes as long as the provided predicate is true
pub(crate) fn read_while<F: Fn(&u8) -> bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> {
match self.remaining.iter().position(|x| !f(x)) {
None => self.read_exact(self.remaining.len()),
Some(position) => self.read_exact(position),
}
}
// Parse an integer out of the ASCII digits
pub(crate) fn read_int<T: FromStr<Err = ParseIntError>>(&mut self) -> Result<T, Error> {
let bytes = self.read_while(u8::is_ascii_digit)?;
Ok(str::from_utf8(bytes)?.parse()?)
}
/// Read bytes until the provided predicate is true
pub(crate) fn read_until<F: Fn(&u8) -> bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> {
match self.remaining.iter().position(f) {
None => self.read_exact(self.remaining.len()),
Some(position) => self.read_exact(position),
}
}
}
pub(crate) fn read_be_i32(bytes: &[u8]) -> Result<i32, Error> {
if bytes.len() != 4 {
return Err(Error::InvalidSlice("too short for i32"));
}
let mut buf = [0; 4];
buf.copy_from_slice(bytes);
Ok(i32::from_be_bytes(buf))
}
pub(crate) fn read_be_i64(bytes: &[u8]) -> Result<i64, Error> {
if bytes.len() != 8 {
return Err(Error::InvalidSlice("too short for i64"));
}
let mut buf = [0; 8];
buf.copy_from_slice(bytes);
Ok(i64::from_be_bytes(buf))
}
/// TZif version
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Version {
/// Version 1
V1,
/// Version 2
V2,
/// Version 3
V3,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,904 @@
//! Types related to a time zone.
use std::fs::{self, File};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::{cmp::Ordering, fmt, str};
use super::rule::{AlternateTime, TransitionRule};
use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
/// Time zone
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct TimeZone {
/// List of transitions
transitions: Vec<Transition>,
/// List of local time types (cannot be empty)
local_time_types: Vec<LocalTimeType>,
/// List of leap seconds
leap_seconds: Vec<LeapSecond>,
/// Extra transition rule applicable after the last transition
extra_rule: Option<TransitionRule>,
}
impl TimeZone {
/// Returns local time zone.
///
/// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead.
///
pub(crate) fn local(env_tz: Option<&str>) -> Result<Self, Error> {
match env_tz {
Some(tz) => Self::from_posix_tz(tz),
None => Self::from_posix_tz("localtime"),
}
}
/// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html).
fn from_posix_tz(tz_string: &str) -> Result<Self, Error> {
if tz_string.is_empty() {
return Err(Error::InvalidTzString("empty TZ string"));
}
if tz_string == "localtime" {
return Self::from_tz_data(&fs::read("/etc/localtime")?);
}
let mut chars = tz_string.chars();
if chars.next() == Some(':') {
return Self::from_file(&mut find_tz_file(chars.as_str())?);
}
if let Ok(mut file) = find_tz_file(tz_string) {
return Self::from_file(&mut file);
}
// TZ string extensions are not allowed
let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?;
Self::new(
vec![],
match rule {
TransitionRule::Fixed(local_time_type) => vec![local_time_type],
TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst],
},
vec![],
Some(rule),
)
}
/// Construct a time zone
pub(super) fn new(
transitions: Vec<Transition>,
local_time_types: Vec<LocalTimeType>,
leap_seconds: Vec<LeapSecond>,
extra_rule: Option<TransitionRule>,
) -> Result<Self, Error> {
let new = Self { transitions, local_time_types, leap_seconds, extra_rule };
new.as_ref().validate()?;
Ok(new)
}
/// Construct a time zone from the contents of a time zone file
fn from_file(file: &mut File) -> Result<Self, Error> {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Self::from_tz_data(&bytes)
}
/// Construct a time zone from the contents of a time zone file
///
/// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536).
pub(crate) fn from_tz_data(bytes: &[u8]) -> Result<Self, Error> {
parser::parse(bytes)
}
/// Construct a time zone with the specified UTC offset in seconds
fn fixed(ut_offset: i32) -> Result<Self, Error> {
Ok(Self {
transitions: Vec::new(),
local_time_types: vec![LocalTimeType::with_offset(ut_offset)?],
leap_seconds: Vec::new(),
extra_rule: None,
})
}
/// Construct the time zone associated to UTC
pub(crate) fn utc() -> Self {
Self {
transitions: Vec::new(),
local_time_types: vec![LocalTimeType::UTC],
leap_seconds: Vec::new(),
extra_rule: None,
}
}
/// Find the local time type associated to the time zone at the specified Unix time in seconds
pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
self.as_ref().find_local_time_type(unix_time)
}
// should we pass NaiveDateTime all the way through to this fn?
pub(crate) fn find_local_time_type_from_local(
&self,
local_time: i64,
year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
self.as_ref().find_local_time_type_from_local(local_time, year)
}
/// Returns a reference to the time zone
fn as_ref(&self) -> TimeZoneRef {
TimeZoneRef {
transitions: &self.transitions,
local_time_types: &self.local_time_types,
leap_seconds: &self.leap_seconds,
extra_rule: &self.extra_rule,
}
}
}
/// Reference to a time zone
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct TimeZoneRef<'a> {
/// List of transitions
transitions: &'a [Transition],
/// List of local time types (cannot be empty)
local_time_types: &'a [LocalTimeType],
/// List of leap seconds
leap_seconds: &'a [LeapSecond],
/// Extra transition rule applicable after the last transition
extra_rule: &'a Option<TransitionRule>,
}
impl<'a> TimeZoneRef<'a> {
/// Find the local time type associated to the time zone at the specified Unix time in seconds
pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> {
let extra_rule = match self.transitions.last() {
None => match self.extra_rule {
Some(extra_rule) => extra_rule,
None => return Ok(&self.local_time_types[0]),
},
Some(last_transition) => {
let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) {
Ok(unix_leap_time) => unix_leap_time,
Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
Err(err) => return Err(err),
};
if unix_leap_time >= last_transition.unix_leap_time {
match self.extra_rule {
Some(extra_rule) => extra_rule,
None => {
return Err(Error::FindLocalTimeType(
"no local time type is available for the specified timestamp",
))
}
}
} else {
let index = match self
.transitions
.binary_search_by_key(&unix_leap_time, Transition::unix_leap_time)
{
Ok(x) => x + 1,
Err(x) => x,
};
let local_time_type_index = if index > 0 {
self.transitions[index - 1].local_time_type_index
} else {
0
};
return Ok(&self.local_time_types[local_time_type_index]);
}
}
};
match extra_rule.find_local_time_type(unix_time) {
Ok(local_time_type) => Ok(local_time_type),
Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
err => err,
}
}
pub(crate) fn find_local_time_type_from_local(
&self,
local_time: i64,
year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
// #TODO: this is wrong as we need 'local_time_to_local_leap_time ?
// but ... does the local time even include leap seconds ??
// let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) {
// Ok(unix_leap_time) => unix_leap_time,
// Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
// Err(err) => return Err(err),
// };
let local_leap_time = local_time;
// if we have at least one transition,
// we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions
if !self.transitions.is_empty() {
let mut prev = Some(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.unwrap().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.unwrap()));
} else if local_leap_time >= transition_end
&& local_leap_time <= transition_start
{
if prev.unwrap().ut_offset < after_ltt.ut_offset {
return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt));
} else {
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap()));
}
}
}
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.unwrap()));
} else if local_leap_time == transition_end {
if prev.unwrap().ut_offset < after_ltt.ut_offset {
return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt));
} else {
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap()));
}
}
}
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.unwrap()));
} 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 = Some(after_ltt);
}
};
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(self.local_time_types[0]))
}
}
/// 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
&& saturating_abs(self.leap_seconds[0].correction) == 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 =
saturating_abs(x1.correction.saturating_sub(x0.correction));
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
fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, Error> {
let mut unix_leap_time = unix_time;
let mut i = 0;
while i < self.leap_seconds.len() {
let leap_second = &self.leap_seconds[i];
if unix_leap_time < leap_second.unix_leap_time {
break;
}
unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) {
Some(unix_leap_time) => unix_leap_time,
None => return Err(Error::OutOfRange("out of range operation")),
};
i += 1;
}
Ok(unix_leap_time)
}
/// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone
fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, Error> {
if unix_leap_time == i64::min_value() {
return Err(Error::OutOfRange("out of range operation"));
}
let index = match self
.leap_seconds
.binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time)
{
Ok(x) => x + 1,
Err(x) => x,
};
let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 };
match unix_leap_time.checked_sub(correction as i64) {
Some(unix_time) => Ok(unix_time),
None => Err(Error::OutOfRange("out of range operation")),
}
}
/// The UTC time zone
const UTC: TimeZoneRef<'static> = TimeZoneRef {
transitions: &[],
local_time_types: &[LocalTimeType::UTC],
leap_seconds: &[],
extra_rule: &None,
};
}
/// Transition of a TZif file
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(super) struct Transition {
/// Unix leap time
unix_leap_time: i64,
/// Index specifying the local time type of the transition
local_time_type_index: usize,
}
impl Transition {
/// Construct a TZif file transition
pub(super) fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self {
Self { unix_leap_time, local_time_type_index }
}
/// Returns Unix leap time
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) fn new(unix_leap_time: i64, correction: i32) -> Self {
Self { unix_leap_time, correction }
}
/// Returns Unix leap time
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
fn new(input: &[u8]) -> Result<Self, Error> {
let len = input.len();
if !(3..=7).contains(&len) {
return Err(Error::LocalTimeType(
"time zone name must have between 3 and 7 characters",
));
}
let mut bytes = [0; 8];
bytes[0] = input.len() as u8;
let mut i = 0;
while i < len {
let b = input[i];
match b {
b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {}
_ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
}
bytes[i + 1] = b;
i += 1;
}
Ok(Self { bytes })
}
/// Returns time zone name as a byte slice
fn as_bytes(&self) -> &[u8] {
match self.bytes[0] {
3 => &self.bytes[1..4],
4 => &self.bytes[1..5],
5 => &self.bytes[1..6],
6 => &self.bytes[1..7],
7 => &self.bytes[1..8],
_ => unreachable!(),
}
}
/// Check if two time zone names are equal
fn equal(&self, other: &Self) -> bool {
self.bytes == other.bytes
}
}
impl AsRef<str> for TimeZoneName {
fn as_ref(&self) -> &str {
// SAFETY: ASCII is valid UTF-8
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}
}
impl fmt::Debug for TimeZoneName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_ref().fmt(f)
}
}
/// Local time type associated to a time zone
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct LocalTimeType {
/// Offset from UTC in seconds
pub(super) ut_offset: i32,
/// Daylight Saving Time indicator
is_dst: bool,
/// Time zone name
name: Option<TimeZoneName>,
}
impl LocalTimeType {
/// Construct a local time type
pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error> {
if ut_offset == i32::min_value() {
return Err(Error::LocalTimeType("invalid UTC offset"));
}
let name = match name {
Some(name) => TimeZoneName::new(name)?,
None => return Ok(Self { ut_offset, is_dst, name: None }),
};
Ok(Self { ut_offset, is_dst, name: Some(name) })
}
/// Construct a local time type with the specified UTC offset in seconds
pub(super) fn with_offset(ut_offset: i32) -> Result<Self, Error> {
if ut_offset == i32::min_value() {
return Err(Error::LocalTimeType("invalid UTC offset"));
}
Ok(Self { ut_offset, is_dst: false, name: None })
}
/// Returns offset from UTC in seconds
pub(crate) fn offset(&self) -> i32 {
self.ut_offset
}
/// Returns daylight saving time indicator
pub(super) fn is_dst(&self) -> bool {
self.is_dst
}
pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None };
}
/// Open the TZif file corresponding to a TZ string
fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
// Don't check system timezone directories on non-UNIX platforms
#[cfg(not(unix))]
return Ok(File::open(path)?);
#[cfg(unix)]
{
let path = path.as_ref();
if path.is_absolute() {
return Ok(File::open(path)?);
}
for folder in &ZONE_INFO_DIRECTORIES {
if let Ok(file) = File::open(PathBuf::from(folder).join(path)) {
return Ok(file);
}
}
Err(Error::Io(io::ErrorKind::NotFound.into()))
}
}
#[inline]
fn saturating_abs(v: i32) -> i32 {
if v.is_positive() {
v
} else if v == i32::min_value() {
i32::max_value()
} else {
-v
}
}
// Possible system timezone directories
#[cfg(unix)]
const ZONE_INFO_DIRECTORIES: [&str; 3] =
["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/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};
use crate::matches;
#[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_tz_ascii_str() -> Result<(), Error> {
assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_))));
assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123");
assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234");
assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345");
assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456");
assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567");
assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"123\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!(matches!(time_zone_3.find_local_time_type(0), Err(Error::FindLocalTimeType(_))));
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);
}
let 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(())
}
}

View File

@@ -0,0 +1,185 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime};
use super::tz_info::TimeZone;
use super::{DateTime, FixedOffset, Local, NaiveDateTime};
use crate::{Datelike, LocalResult, Utc};
pub(super) fn now() -> DateTime<Local> {
let now = Utc::now().naive_utc();
naive_to_local(&now, false).unwrap()
}
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
TZ_INFO.with(|maybe_cache| {
maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local)
})
}
// we have to store the `Cache` in an option as it can't
// be initalized in a static context.
thread_local! {
static TZ_INFO: RefCell<Option<Cache>> = Default::default();
}
enum Source {
LocalTime { mtime: SystemTime },
Environment { hash: u64 },
}
impl Source {
fn new(env_tz: Option<&str>) -> Source {
match env_tz {
Some(tz) => {
let mut hasher = hash_map::DefaultHasher::new();
hasher.write(tz.as_bytes());
let hash = hasher.finish();
Source::Environment { hash }
}
None => match fs::symlink_metadata("/etc/localtime") {
Ok(data) => Source::LocalTime {
// we have to pick a sensible default when the mtime fails
// by picking SystemTime::now() we raise the probability of
// the cache being invalidated if/when the mtime starts working
mtime: data.modified().unwrap_or_else(|_| SystemTime::now()),
},
Err(_) => {
// as above, now() should be a better default than some constant
// TODO: see if we can improve caching in the case where the fallback is a valid timezone
Source::LocalTime { mtime: SystemTime::now() }
}
},
}
}
}
struct Cache {
zone: TimeZone,
source: Source,
last_checked: SystemTime,
}
#[cfg(target_os = "android")]
const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo";
#[cfg(target_os = "aix")]
const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";
#[allow(dead_code)] // keeps the cfg simpler
#[cfg(not(any(target_os = "android", target_os = "aix")))]
const TZDB_LOCATION: &str = "/usr/share/zoneinfo";
fn fallback_timezone() -> Option<TimeZone> {
let tz_name = iana_time_zone::get_timezone().ok()?;
let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, 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_ref().map(|s| s.as_str());
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<DateTime<Local>> {
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_ref().map(|s| s.as_str());
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(DateTime::from_utc(d, 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 anywa.
match self
.zone
.find_local_time_type_from_local(d.timestamp(), d.year())
.expect("unable to select local time type")
{
LocalResult::None => LocalResult::None,
LocalResult::Ambiguous(early, late) => {
let early_offset = FixedOffset::east_opt(early.offset()).unwrap();
let late_offset = FixedOffset::east_opt(late.offset()).unwrap();
LocalResult::Ambiguous(
DateTime::from_utc(d - early_offset, early_offset),
DateTime::from_utc(d - late_offset, late_offset),
)
}
LocalResult::Single(tt) => {
let offset = FixedOffset::east_opt(tt.offset()).unwrap();
LocalResult::Single(DateTime::from_utc(d - offset, offset))
}
}
}
}

View File

@@ -0,0 +1,278 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::io;
use std::mem;
use std::time::{SystemTime, UNIX_EPOCH};
use winapi::shared::minwindef::*;
use winapi::um::minwinbase::SYSTEMTIME;
use winapi::um::timezoneapi::*;
use super::{FixedOffset, Local};
use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
pub(super) fn now() -> DateTime<Local> {
tm_to_datetime(Timespec::now().local())
}
/// Converts a local `NaiveDateTime` to the `time::Timespec`.
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
let tm = Tm {
tm_sec: d.second() as i32,
tm_min: d.minute() as i32,
tm_hour: d.hour() as i32,
tm_mday: d.day() as i32,
tm_mon: d.month0() as i32, // yes, C is that strange...
tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`.
tm_wday: 0, // to_local ignores this
tm_yday: 0, // and this
tm_isdst: -1,
// This seems pretty fake?
tm_utcoff: if local { 1 } else { 0 },
// do not set this, OS APIs are heavily inconsistent in terms of leap second handling
tm_nsec: 0,
};
let spec = Timespec {
sec: match local {
false => utc_tm_to_time(&tm),
true => local_tm_to_time(&tm),
},
nsec: tm.tm_nsec,
};
// Adjust for leap seconds
let mut tm = spec.local();
assert_eq!(tm.tm_nsec, 0);
tm.tm_nsec = d.nanosecond() as i32;
// #TODO - there should be ambiguous cases, investigate?
LocalResult::Single(tm_to_datetime(tm))
}
/// Converts a `time::Tm` struct into the timezone-aware `DateTime`.
fn tm_to_datetime(mut tm: Tm) -> DateTime<Local> {
if tm.tm_sec >= 60 {
tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000;
tm.tm_sec = 59;
}
let date = NaiveDate::from_ymd_opt(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32)
.unwrap();
let time = NaiveTime::from_hms_nano(
tm.tm_hour as u32,
tm.tm_min as u32,
tm.tm_sec as u32,
tm.tm_nsec as u32,
);
let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap();
DateTime::from_utc(date.and_time(time) - offset, offset)
}
/// A record specifying a time value in seconds and nanoseconds, where
/// nanoseconds represent the offset from the given second.
///
/// For example a timespec of 1.2 seconds after the beginning of the epoch would
/// be represented as {sec: 1, nsec: 200000000}.
struct Timespec {
sec: i64,
nsec: i32,
}
impl Timespec {
/// Constructs a timespec representing the current time in UTC.
fn now() -> Timespec {
let st =
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 }
}
/// Converts this timespec into the system's local time.
fn local(self) -> Tm {
let mut tm = Tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_utcoff: 0,
tm_nsec: 0,
};
time_to_local_tm(self.sec, &mut tm);
tm.tm_nsec = self.nsec;
tm
}
}
/// Holds a calendar date and time broken down into its components (year, month,
/// day, and so on), also called a broken-down time value.
// FIXME: use c_int instead of i32?
#[repr(C)]
struct Tm {
/// Seconds after the minute - [0, 60]
tm_sec: i32,
/// Minutes after the hour - [0, 59]
tm_min: i32,
/// Hours after midnight - [0, 23]
tm_hour: i32,
/// Day of the month - [1, 31]
tm_mday: i32,
/// Months since January - [0, 11]
tm_mon: i32,
/// Years since 1900
tm_year: i32,
/// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
tm_wday: i32,
/// Days since January 1 - [0, 365]
tm_yday: i32,
/// Daylight Saving Time flag.
///
/// This value is positive if Daylight Saving Time is in effect, zero if
/// Daylight Saving Time is not in effect, and negative if this information
/// is not available.
tm_isdst: i32,
/// Identifies the time zone that was used to compute this broken-down time
/// value, including any adjustment for Daylight Saving Time. This is the
/// number of seconds east of UTC. For example, for U.S. Pacific Daylight
/// Time, the value is `-7*60*60 = -25200`.
tm_utcoff: i32,
/// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
tm_nsec: i32,
}
const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
fn time_to_file_time(sec: i64) -> FILETIME {
let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64;
FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD }
}
fn file_time_as_u64(ft: &FILETIME) -> u64 {
((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64)
}
fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 {
let t = file_time_as_u64(ft) as i64;
((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64
}
fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME {
unsafe {
let mut ft = mem::zeroed();
SystemTimeToFileTime(sys, &mut ft);
ft
}
}
fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME {
let mut sys: SYSTEMTIME = unsafe { mem::zeroed() };
sys.wSecond = tm.tm_sec as WORD;
sys.wMinute = tm.tm_min as WORD;
sys.wHour = tm.tm_hour as WORD;
sys.wDay = tm.tm_mday as WORD;
sys.wDayOfWeek = tm.tm_wday as WORD;
sys.wMonth = (tm.tm_mon + 1) as WORD;
sys.wYear = (tm.tm_year + 1900) as WORD;
sys
}
fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) {
tm.tm_sec = sys.wSecond as i32;
tm.tm_min = sys.wMinute as i32;
tm.tm_hour = sys.wHour as i32;
tm.tm_mday = sys.wDay as i32;
tm.tm_wday = sys.wDayOfWeek as i32;
tm.tm_mon = (sys.wMonth - 1) as i32;
tm.tm_year = (sys.wYear - 1900) as i32;
tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
fn yday(year: i32, month: i32, day: i32) -> i32 {
let leap = if month > 2 {
if year % 4 == 0 {
1
} else {
2
}
} else {
0
};
let july = if month > 7 { 1 } else { 0 };
(month - 1) * 30 + month / 2 + (day - 1) - leap + july
}
}
macro_rules! call {
($name:ident($($arg:expr),*)) => {
if $name($($arg),*) == 0 {
panic!(concat!(stringify!($name), " failed with: {}"),
io::Error::last_os_error());
}
}
}
fn time_to_local_tm(sec: i64, tm: &mut Tm) {
let ft = time_to_file_time(sec);
unsafe {
let mut utc = mem::zeroed();
let mut local = mem::zeroed();
call!(FileTimeToSystemTime(&ft, &mut utc));
call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local));
system_time_to_tm(&local, tm);
let local = system_time_to_file_time(&local);
let local_sec = file_time_to_unix_seconds(&local);
let mut tz = mem::zeroed();
GetTimeZoneInformation(&mut tz);
// SystemTimeToTzSpecificLocalTime already applied the biases so
// check if it non standard
tm.tm_utcoff = (local_sec - sec) as i32;
tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 };
}
}
fn utc_tm_to_time(tm: &Tm) -> i64 {
unsafe {
let mut ft = mem::zeroed();
let sys_time = tm_to_system_time(tm);
call!(SystemTimeToFileTime(&sys_time, &mut ft));
file_time_to_unix_seconds(&ft)
}
}
fn local_tm_to_time(tm: &Tm) -> i64 {
unsafe {
let mut ft = mem::zeroed();
let mut utc = mem::zeroed();
let mut sys_time = tm_to_system_time(tm);
call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc));
call!(SystemTimeToFileTime(&utc, &mut ft));
file_time_to_unix_seconds(&ft)
}
}

View File

@@ -0,0 +1,549 @@
// 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<Tz>`
//! 2. Converting the UTC `NaiveDateTime` to `DateTime<Tz>`
//! 3. Converting `DateTime<Tz>` to the local `NaiveDateTime`
//! 4. Constructing `DateTime<Tz>` 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<T> {
/// 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<T> LocalResult<T> {
/// Returns `Some` only when the conversion result is unique, or `None` otherwise.
pub fn single(self) -> Option<T> {
match self {
LocalResult::Single(t) => Some(t),
_ => None,
}
}
/// Returns `Some` for the earliest possible conversion result, or `None` if none.
pub fn earliest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t),
_ => None,
}
}
/// Returns `Some` for the latest possible conversion result, or `None` if none.
pub fn latest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t),
_ => None,
}
}
/// Maps a `LocalResult<T>` into `LocalResult<U>` with given function.
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> LocalResult<U> {
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<Tz: TimeZone> LocalResult<Date<Tz>> {
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
/// The offset in the current date is preserved.
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
pub fn and_time(self, time: NaiveTime) -> LocalResult<DateTime<Tz>> {
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]
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> {
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]
pub fn and_hms_milli_opt(
self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> LocalResult<DateTime<Tz>> {
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]
pub fn and_hms_micro_opt(
self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> LocalResult<DateTime<Tz>> {
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]
pub fn and_hms_nano_opt(
self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> LocalResult<DateTime<Tz>> {
match self {
LocalResult::Single(d) => d
.and_hms_nano_opt(hour, min, sec, nano)
.map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None,
}
}
}
impl<T: fmt::Debug> LocalResult<T> {
/// Returns the single unique conversion result, or panics accordingly.
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<DateTime<Self>> {
match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec))
{
Some(dt) => self.from_local_datetime(&dt),
None => LocalResult::None,
}
}
/// Makes a new `Date` from year, month, day and the current time zone.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
///
/// 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> {
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<Date<Self>> {
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> {
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<Date<Self>> {
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> {
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<Date<Self>> {
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.
///
/// Panics on the out-of-range number of seconds and/or invalid nanosecond,
/// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt).
#[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")]
fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime<Self> {
self.timestamp_opt(secs, nsecs).unwrap()
}
/// 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.
///
/// Returns `LocalResult::None` on out-of-range number of seconds and/or
/// invalid nanosecond, otherwise always returns `LocalResult::Single`.
///
/// # Example
///
/// ```
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC");
/// ```
fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult<DateTime<Self>> {
match NaiveDateTime::from_timestamp_opt(secs, nsecs) {
Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
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> {
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<DateTime<Self>> {
let (mut secs, mut millis) = (millis / 1000, millis % 1000);
if millis < 0 {
secs -= 1;
millis += 1000;
}
self.timestamp_opt(secs, millis as u32 * 1_000_000)
}
/// Makes a new `DateTime` from the number of non-leap nanoseconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
///
/// Unlike [`timestamp_millis`](#method.timestamp_millis), this never
/// panics.
///
/// # Example
///
/// ```
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648);
/// ```
fn timestamp_nanos(&self, nanos: i64) -> DateTime<Self> {
let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000);
if nanos < 0 {
secs -= 1;
nanos += 1_000_000_000;
}
self.timestamp_opt(secs, nanos as u32).unwrap()
}
/// Parses a string with the specified format string and returns a
/// `DateTime` with the current offset.
///
/// See the [`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`].
fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult<DateTime<Self>> {
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<Self::Offset>;
/// Creates the offset(s) for given local `NaiveDateTime` if possible.
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset>;
/// Converts the local `NaiveDate` to the timezone-aware `Date` if possible.
#[allow(clippy::wrong_self_convention)]
#[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")]
#[allow(deprecated)]
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Self>> {
self.offset_from_local_date(local).map(|offset| {
// since FixedOffset is within +/- 1 day, the date is never affected
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<DateTime<Self>> {
self.offset_from_local_datetime(local)
.map(|offset| DateTime::from_utc(*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<Self> {
Date::from_utc(*utc, self.offset_from_utc_date(utc))
}
/// Converts the UTC `NaiveDateTime` to the local time.
/// The UTC is continuous and thus this cannot fail (but can give the duplicate local time).
#[allow(clippy::wrong_self_convention)]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc))
}
}
#[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());
}
}

View File

@@ -0,0 +1,125 @@
// 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<Utc>`
/// instances.
///
/// # Example
///
/// ```
/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc};
///
/// let dt = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc);
///
/// assert_eq!(Utc.timestamp(61, 0), dt);
/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt);
/// ```
#[derive(Copy, Clone, PartialEq, Eq)]
#[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)]
pub fn today() -> Date<Utc> {
Utc::now().date()
}
/// Returns a `DateTime` which corresponds to the current date and time.
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
pub fn now() -> DateTime<Utc> {
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();
DateTime::from_utc(naive, Utc)
}
/// 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"))
))]
pub fn now() -> DateTime<Utc> {
let now = js_sys::Date::new_0();
DateTime::<Utc>::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<Utc> {
LocalResult::Single(Utc)
}
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Utc> {
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")
}
}

View File

@@ -0,0 +1,765 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Temporal quantification
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::time::Duration as StdDuration;
use core::{fmt, i64};
#[cfg(any(feature = "std", test))]
use std::error::Error;
#[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 = 1000_000;
/// The number of nanoseconds in seconds.
const NANOS_PER_SEC: i32 = 1_000_000_000;
/// The number of microseconds per second.
const MICROS_PER_SEC: i64 = 1000_000;
/// The number of milliseconds per second.
const MILLIS_PER_SEC: i64 = 1000;
/// The number of seconds in a minute.
const SECS_PER_MINUTE: i64 = 60;
/// The number of seconds in an hour.
const SECS_PER_HOUR: i64 = 3600;
/// The number of (non-leap) seconds in days.
const SECS_PER_DAY: i64 = 86400;
/// The number of (non-leap) seconds in a week.
const SECS_PER_WEEK: i64 = 604800;
macro_rules! try_opt {
($e:expr) => {
match $e {
Some(v) => v,
None => return None,
}
};
}
/// ISO 8601 time duration with nanosecond precision.
///
/// This also allows for the negative duration; see individual methods for details.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[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]
pub fn weeks(weeks: i64) -> Duration {
let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds");
Duration::seconds(secs)
}
/// Makes a new `Duration` with given number of days.
/// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
pub fn days(days: i64) -> Duration {
let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds");
Duration::seconds(secs)
}
/// Makes a new `Duration` with given number of hours.
/// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
pub fn hours(hours: i64) -> Duration {
let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds");
Duration::seconds(secs)
}
/// Makes a new `Duration` with given number of minutes.
/// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
pub fn minutes(minutes: i64) -> Duration {
let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds");
Duration::seconds(secs)
}
/// Makes a new `Duration` with given number of seconds.
/// Panics when the duration is more than `i64::MAX` seconds
/// or less than `i64::MIN` seconds.
#[inline]
pub fn seconds(seconds: i64) -> Duration {
let d = Duration { secs: seconds, nanos: 0 };
if d < MIN || d > MAX {
panic!("Duration::seconds out of bounds");
}
d
}
/// Makes a new `Duration` with given number of milliseconds.
#[inline]
pub fn milliseconds(milliseconds: i64) -> Duration {
let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC);
let nanos = millis as i32 * NANOS_PER_MILLI;
Duration { secs: secs, nanos: nanos }
}
/// Makes a new `Duration` with given number of microseconds.
#[inline]
pub fn microseconds(microseconds: i64) -> Duration {
let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
let nanos = micros as i32 * NANOS_PER_MICRO;
Duration { secs: secs, nanos: nanos }
}
/// Makes a new `Duration` with given number of nanoseconds.
#[inline]
pub fn nanoseconds(nanos: i64) -> Duration {
let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64);
Duration { secs: secs, nanos: nanos as i32 }
}
/// Returns the total number of whole weeks in the duration.
#[inline]
pub fn num_weeks(&self) -> i64 {
self.num_days() / 7
}
/// Returns the total number of whole days in the duration.
pub fn num_days(&self) -> i64 {
self.num_seconds() / SECS_PER_DAY
}
/// Returns the total number of whole hours in the duration.
#[inline]
pub fn num_hours(&self) -> i64 {
self.num_seconds() / SECS_PER_HOUR
}
/// Returns the total number of whole minutes in the duration.
#[inline]
pub fn num_minutes(&self) -> i64 {
self.num_seconds() / SECS_PER_MINUTE
}
/// Returns the total number of whole seconds in the duration.
pub fn num_seconds(&self) -> i64 {
// If secs is negative, nanos should be subtracted from the duration.
if self.secs < 0 && self.nanos > 0 {
self.secs + 1
} else {
self.secs
}
}
/// Returns the number of nanoseconds such that
/// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of
/// nanoseconds in the duration.
fn nanos_mod_sec(&self) -> i32 {
if self.secs < 0 && self.nanos > 0 {
self.nanos - NANOS_PER_SEC
} else {
self.nanos
}
}
/// Returns the total number of whole milliseconds in the duration,
pub fn num_milliseconds(&self) -> i64 {
// A proper Duration will not overflow, because MIN and MAX are defined
// such that the range is exactly i64 milliseconds.
let secs_part = self.num_seconds() * MILLIS_PER_SEC;
let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI;
secs_part + nanos_part as i64
}
/// Returns the total number of whole microseconds in the duration,
/// or `None` on overflow (exceeding 2^63 microseconds in either direction).
pub fn num_microseconds(&self) -> Option<i64> {
let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC));
let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO;
secs_part.checked_add(nanos_part as i64)
}
/// Returns the total number of whole nanoseconds in the duration,
/// or `None` on overflow (exceeding 2^63 nanoseconds in either direction).
pub fn num_nanoseconds(&self) -> Option<i64> {
let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64));
let nanos_part = self.nanos_mod_sec();
secs_part.checked_add(nanos_part as i64)
}
/// Add two durations, returning `None` if overflow occurred.
pub fn checked_add(&self, rhs: &Duration) -> Option<Duration> {
let mut secs = try_opt!(self.secs.checked_add(rhs.secs));
let mut nanos = self.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs = try_opt!(secs.checked_add(1));
}
let d = Duration { secs: secs, nanos: nanos };
// Even if d is within the bounds of i64 seconds,
// it might still overflow i64 milliseconds.
if d < MIN || d > MAX {
None
} else {
Some(d)
}
}
/// Subtract two durations, returning `None` if overflow occurred.
pub fn checked_sub(&self, rhs: &Duration) -> Option<Duration> {
let mut secs = try_opt!(self.secs.checked_sub(rhs.secs));
let mut nanos = self.nanos - rhs.nanos;
if nanos < 0 {
nanos += NANOS_PER_SEC;
secs = try_opt!(secs.checked_sub(1));
}
let d = Duration { secs: secs, nanos: nanos };
// Even if d is within the bounds of i64 seconds,
// it might still overflow i64 milliseconds.
if d < MIN || d > MAX {
None
} else {
Some(d)
}
}
/// Returns the duration as an absolute (non-negative) value.
#[inline]
pub fn abs(&self) -> Duration {
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 fn min_value() -> Duration {
MIN
}
/// The maximum possible `Duration`: `i64::MAX` milliseconds.
#[inline]
pub fn max_value() -> Duration {
MAX
}
/// A duration where the stored seconds and nanoseconds are equal to zero.
#[inline]
pub fn zero() -> Duration {
Duration { secs: 0, nanos: 0 }
}
/// Returns `true` if the duration equals `Duration::zero()`.
#[inline]
pub fn is_zero(&self) -> bool {
self.secs == 0 && self.nanos == 0
}
/// Creates a `time::Duration` object from `std::time::Duration`
///
/// This function errors when original duration is larger than the maximum
/// value supported for this type.
pub fn from_std(duration: StdDuration) -> Result<Duration, OutOfRangeError> {
// We need to check secs as u64 before coercing to i64
if duration.as_secs() > MAX.secs as u64 {
return Err(OutOfRangeError(()));
}
let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 };
if d > MAX {
return Err(OutOfRangeError(()));
}
Ok(d)
}
/// Creates a `std::time::Duration` object from `time::Duration`
///
/// This function errors when duration is less than zero. As standard
/// library implementation is limited to non-negative values.
pub fn to_std(&self) -> Result<StdDuration, OutOfRangeError> {
if self.secs < 0 {
return Err(OutOfRangeError(()));
}
Ok(StdDuration::new(self.secs as u64, self.nanos as u32))
}
}
impl Neg for Duration {
type Output = Duration;
#[inline]
fn neg(self) -> Duration {
if self.nanos == 0 {
Duration { secs: -self.secs, nanos: 0 }
} else {
Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos }
}
}
}
impl Add for Duration {
type Output = Duration;
fn add(self, rhs: Duration) -> Duration {
let mut secs = self.secs + rhs.secs;
let mut nanos = self.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs += 1;
}
Duration { secs: secs, nanos: nanos }
}
}
impl Sub for Duration {
type Output = Duration;
fn sub(self, rhs: Duration) -> Duration {
let mut secs = self.secs - rhs.secs;
let mut nanos = self.nanos - rhs.nanos;
if nanos < 0 {
nanos += NANOS_PER_SEC;
secs -= 1;
}
Duration { secs: secs, nanos: nanos }
}
}
impl Mul<i32> for Duration {
type Output = Duration;
fn mul(self, rhs: i32) -> Duration {
// Multiply nanoseconds as i64, because it cannot overflow that way.
let total_nanos = self.nanos as i64 * rhs as i64;
let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
let secs = self.secs * rhs as i64 + extra_secs;
Duration { secs: secs, nanos: nanos as i32 }
}
}
impl Div<i32> for Duration {
type Output = Duration;
fn div(self, rhs: i32) -> Duration {
let mut secs = self.secs / rhs as i64;
let carry = self.secs - secs * rhs as i64;
let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
let mut nanos = self.nanos / rhs + extra_nanos as i32;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs += 1;
}
if nanos < 0 {
nanos += NANOS_PER_SEC;
secs -= 1;
}
Duration { secs: secs, nanos: nanos }
}
}
#[cfg(any(feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl<'a> std::iter::Sum<&'a Duration> for Duration {
fn sum<I: Iterator<Item = &'a Duration>>(iter: I) -> Duration {
iter.fold(Duration::zero(), |acc, x| acc + *x)
}
}
#[cfg(any(feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::iter::Sum<Duration> for Duration {
fn sum<I: Iterator<Item = Duration>>(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(any(feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl Error for OutOfRangeError {
#[allow(deprecated)]
fn description(&self) -> &str {
"out of range error"
}
}
// Copied from libnum
#[inline]
fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) {
(div_floor_64(this, other), mod_floor_64(this, other))
}
#[inline]
fn div_floor_64(this: i64, other: i64) -> i64 {
match div_rem_64(this, other) {
(d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1,
(d, _) => d,
}
}
#[inline]
fn mod_floor_64(this: i64, other: i64) -> i64 {
match this % other {
r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other,
r => r,
}
}
#[inline]
fn div_rem_64(this: i64, other: i64) -> (i64, i64) {
(this / other, this % other)
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Duration {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Duration> {
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::{Duration, OutOfRangeError, MAX, MIN};
use std::time::Duration as StdDuration;
use std::{i32, i64};
#[test]
fn test_duration() {
assert!(Duration::seconds(1) != Duration::zero());
assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3));
assert_eq!(
Duration::seconds(86399) + Duration::seconds(4),
Duration::days(1) + Duration::seconds(3)
);
assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000));
assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000));
assert_eq!(
Duration::days(2) + Duration::seconds(86399) + Duration::nanoseconds(1234567890),
Duration::days(3) + Duration::nanoseconds(234567890)
);
assert_eq!(-Duration::days(3), Duration::days(-3));
assert_eq!(
-(Duration::days(3) + Duration::seconds(70)),
Duration::days(-4) + Duration::seconds(86400 - 70)
);
}
#[test]
fn test_duration_num_days() {
assert_eq!(Duration::zero().num_days(), 0);
assert_eq!(Duration::days(1).num_days(), 1);
assert_eq!(Duration::days(-1).num_days(), -1);
assert_eq!(Duration::seconds(86399).num_days(), 0);
assert_eq!(Duration::seconds(86401).num_days(), 1);
assert_eq!(Duration::seconds(-86399).num_days(), 0);
assert_eq!(Duration::seconds(-86401).num_days(), -1);
assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64);
assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64);
}
#[test]
fn test_duration_num_seconds() {
assert_eq!(Duration::zero().num_seconds(), 0);
assert_eq!(Duration::seconds(1).num_seconds(), 1);
assert_eq!(Duration::seconds(-1).num_seconds(), -1);
assert_eq!(Duration::milliseconds(999).num_seconds(), 0);
assert_eq!(Duration::milliseconds(1001).num_seconds(), 1);
assert_eq!(Duration::milliseconds(-999).num_seconds(), 0);
assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1);
}
#[test]
fn test_duration_num_milliseconds() {
assert_eq!(Duration::zero().num_milliseconds(), 0);
assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1);
assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1);
assert_eq!(Duration::microseconds(999).num_milliseconds(), 0);
assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1);
assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0);
assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1);
assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX);
assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN);
assert_eq!(MAX.num_milliseconds(), i64::MAX);
assert_eq!(MIN.num_milliseconds(), i64::MIN);
}
#[test]
fn test_duration_num_microseconds() {
assert_eq!(Duration::zero().num_microseconds(), Some(0));
assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1));
assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1));
assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0));
assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1));
assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0));
assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1));
assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX));
assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN));
assert_eq!(MAX.num_microseconds(), None);
assert_eq!(MIN.num_microseconds(), None);
// overflow checks
const MICROS_PER_DAY: i64 = 86400_000_000;
assert_eq!(
Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(),
Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY)
);
assert_eq!(
Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(),
Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY)
);
assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None);
assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None);
}
#[test]
fn test_duration_num_nanoseconds() {
assert_eq!(Duration::zero().num_nanoseconds(), Some(0));
assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1));
assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1));
assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX));
assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN));
assert_eq!(MAX.num_nanoseconds(), None);
assert_eq!(MIN.num_nanoseconds(), None);
// overflow checks
const NANOS_PER_DAY: i64 = 86400_000_000_000;
assert_eq!(
Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(),
Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY)
);
assert_eq!(
Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(),
Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY)
);
assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None);
assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None);
}
#[test]
fn test_duration_checked_ops() {
assert_eq!(
Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)),
Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999))
);
assert!(Duration::milliseconds(i64::MAX)
.checked_add(&Duration::microseconds(1000))
.is_none());
assert_eq!(
Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)),
Some(Duration::milliseconds(i64::MIN))
);
assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none());
}
#[test]
fn test_duration_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]
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_vec = vec![
Duration::zero(),
Duration::seconds(1),
Duration::seconds(6),
Duration::seconds(10),
];
let sum_3: Duration = duration_vec.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(-86401).to_string(), "-P1DT1S");
assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S");
// the format specifier should have no effect on `Duration`
assert_eq!(
format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)),
"P1DT2.345S"
);
}
#[test]
fn test_to_std() {
assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0)));
assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0)));
assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000)));
assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000)));
assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777)));
assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000)));
assert_eq!(Duration::seconds(-1).to_std(), Err(OutOfRangeError(())));
assert_eq!(Duration::milliseconds(-1).to_std(), Err(OutOfRangeError(())));
}
#[test]
fn test_from_std() {
assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0)));
assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0)));
assert_eq!(
Ok(Duration::milliseconds(123)),
Duration::from_std(StdDuration::new(0, 123000000))
);
assert_eq!(
Ok(Duration::milliseconds(123765)),
Duration::from_std(StdDuration::new(123, 765000000))
);
assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777)));
assert_eq!(Ok(MAX), Duration::from_std(StdDuration::new(9223372036854775, 807000000)));
assert_eq!(
Duration::from_std(StdDuration::new(9223372036854776, 0)),
Err(OutOfRangeError(()))
);
assert_eq!(
Duration::from_std(StdDuration::new(9223372036854775, 807000001)),
Err(OutOfRangeError(()))
);
}
}

View File

@@ -0,0 +1,763 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
use crate::datetime::DateTime;
use crate::oldtime::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::{DateTime, SubsecRound, Timelike, TimeZone, 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::{DateTime, SubsecRound, Timelike, TimeZone, 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<T> SubsecRound for T
where
T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>,
{
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.
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(any(feature = "std", test))]
type Err: std::error::Error;
/// Error that can occur in rounding or truncating
#[cfg(not(any(feature = "std", test)))]
type Err: fmt::Debug + fmt::Display;
/// Return a copy rounded by Duration.
///
/// # Example
/// ``` rust
/// # use chrono::{DateTime, DurationRound, Duration, TimeZone, 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<Self, Self::Err>;
/// Return a copy truncated by Duration.
///
/// # Example
/// ``` rust
/// # use chrono::{DateTime, DurationRound, Duration, TimeZone, 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<Self, Self::Err>;
}
/// The maximum number of seconds a DateTime can be to be represented as nanoseconds
const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036;
impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
type Err = RoundingError;
fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> {
duration_round(self.naive_local(), self, duration)
}
fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> {
duration_trunc(self.naive_local(), self, duration)
}
}
impl DurationRound for NaiveDateTime {
type Err = RoundingError;
fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> {
duration_round(self, self, duration)
}
fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> {
duration_trunc(self, self, duration)
}
}
fn duration_round<T>(
naive: NaiveDateTime,
original: T,
duration: Duration,
) -> Result<T, RoundingError>
where
T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>,
{
if let Some(span) = duration.num_nanoseconds() {
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
return Err(RoundingError::TimestampExceedsLimit);
}
let stamp = naive.timestamp_nanos();
if span > stamp.abs() {
return Err(RoundingError::DurationExceedsTimestamp);
}
if span == 0 {
return 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<T>(
naive: NaiveDateTime,
original: T,
duration: Duration,
) -> Result<T, RoundingError>
where
T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>,
{
if let Some(span) = duration.num_nanoseconds() {
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
return Err(RoundingError::TimestampExceedsLimit);
}
let stamp = naive.timestamp_nanos();
if span > stamp.abs() {
return Err(RoundingError::DurationExceedsTimestamp);
}
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::{DateTime, 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::{DateTime, DurationRound, Duration, RoundingError, TimeZone, 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::{DateTime, 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(any(feature = "std", test))]
#[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, SubsecRound};
use crate::offset::{FixedOffset, TimeZone, Utc};
use crate::NaiveDate;
use crate::Timelike;
#[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"
);
}
}

View File

@@ -0,0 +1,241 @@
use crate::{IsoWeek, Weekday};
/// The common set of methods for date component.
pub trait Datelike: Sized {
/// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date).
fn year(&self) -> i32;
/// Returns the absolute year number starting from 1 with a boolean flag,
/// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD).
#[inline]
fn year_ce(&self) -> (bool, u32) {
let year = self.year();
if year < 1 {
(false, (1 - year) as u32)
} else {
(true, year as u32)
}
}
/// Returns the month number starting from 1.
///
/// The return value ranges from 1 to 12.
fn month(&self) -> u32;
/// Returns the month number starting from 0.
///
/// The return value ranges from 0 to 11.
fn month0(&self) -> u32;
/// Returns the day of month starting from 1.
///
/// The return value ranges from 1 to 31. (The last day of month differs by months.)
fn day(&self) -> u32;
/// Returns the day of month starting from 0.
///
/// The return value ranges from 0 to 30. (The last day of month differs by months.)
fn day0(&self) -> u32;
/// Returns the day of year starting from 1.
///
/// The return value ranges from 1 to 366. (The last day of year differs by years.)
fn ordinal(&self) -> u32;
/// Returns the day of year starting from 0.
///
/// The return value ranges from 0 to 365. (The last day of year differs by years.)
fn ordinal0(&self) -> u32;
/// Returns the day of week.
fn weekday(&self) -> Weekday;
/// Returns the ISO week.
fn iso_week(&self) -> IsoWeek;
/// Makes a new value with the year number changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_year(&self, year: i32) -> Option<Self>;
/// Makes a new value with the month number (starting from 1) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_month(&self, month: u32) -> Option<Self>;
/// Makes a new value with the month number (starting from 0) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_month0(&self, month0: u32) -> Option<Self>;
/// Makes a new value with the day of month (starting from 1) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_day(&self, day: u32) -> Option<Self>;
/// Makes a new value with the day of month (starting from 0) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_day0(&self, day0: u32) -> Option<Self>;
/// Makes a new value with the day of year (starting from 1) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_ordinal(&self, ordinal: u32) -> Option<Self>;
/// Makes a new value with the day of year (starting from 0) changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_ordinal0(&self, ordinal0: u32) -> Option<Self>;
/// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1.
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Datelike};
///
/// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163);
/// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366);
/// assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1);
/// assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), -365);
/// ```
fn num_days_from_ce(&self) -> i32 {
// See test_num_days_from_ce_against_alternative_impl below for a more straightforward
// implementation.
// we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range.
let mut year = self.year() - 1;
let mut ndays = 0;
if year < 0 {
let excess = 1 + (-year) / 400;
year += excess * 400;
ndays -= excess * 146_097;
}
let div_100 = year / 100;
ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2);
ndays + self.ordinal() as i32
}
}
/// The common set of methods for time component.
pub trait Timelike: Sized {
/// Returns the hour number from 0 to 23.
fn hour(&self) -> u32;
/// Returns the hour number from 1 to 12 with a boolean flag,
/// which is false for AM and true for PM.
#[inline]
fn hour12(&self) -> (bool, u32) {
let hour = self.hour();
let mut hour12 = hour % 12;
if hour12 == 0 {
hour12 = 12;
}
(hour >= 12, hour12)
}
/// Returns the minute number from 0 to 59.
fn minute(&self) -> u32;
/// Returns the second number from 0 to 59.
fn second(&self) -> u32;
/// Returns the number of nanoseconds since the whole non-leap second.
/// The range from 1,000,000,000 to 1,999,999,999 represents
/// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling).
fn nanosecond(&self) -> u32;
/// Makes a new value with the hour number changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_hour(&self, hour: u32) -> Option<Self>;
/// Makes a new value with the minute number changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_minute(&self, min: u32) -> Option<Self>;
/// Makes a new value with the second number changed.
///
/// Returns `None` when the resulting value would be invalid.
/// As with the [`second`](#tymethod.second) method,
/// the input range is restricted to 0 through 59.
fn with_second(&self, sec: u32) -> Option<Self>;
/// Makes a new value with nanoseconds since the whole non-leap second changed.
///
/// Returns `None` when the resulting value would be invalid.
/// As with the [`nanosecond`](#tymethod.nanosecond) method,
/// the input range can exceed 1,000,000,000 for leap seconds.
fn with_nanosecond(&self, nano: u32) -> Option<Self>;
/// Returns the number of non-leap seconds past the last midnight.
#[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: Datelike>(date: &Date) -> i32 {
let year = date.year();
let diff = move |div| in_between(1, year, div);
// 365 days a year, one more in leap years. In the gregorian calendar, leap years are all
// the multiples of 4 except multiples of 100 but including multiples of 400.
date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400)
}
use num_iter::range_inclusive;
for year in range_inclusive(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
);
}
}
}

View File

@@ -0,0 +1,312 @@
use core::fmt;
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};
/// 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.
#[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]
pub fn succ(&self) -> Weekday {
match *self {
Weekday::Mon => Weekday::Tue,
Weekday::Tue => Weekday::Wed,
Weekday::Wed => Weekday::Thu,
Weekday::Thu => Weekday::Fri,
Weekday::Fri => Weekday::Sat,
Weekday::Sat => Weekday::Sun,
Weekday::Sun => Weekday::Mon,
}
}
/// The previous day in the week.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat`
#[inline]
pub fn pred(&self) -> Weekday {
match *self {
Weekday::Mon => Weekday::Sun,
Weekday::Tue => Weekday::Mon,
Weekday::Wed => Weekday::Tue,
Weekday::Thu => Weekday::Wed,
Weekday::Fri => Weekday::Thu,
Weekday::Sat => Weekday::Fri,
Weekday::Sun => Weekday::Sat,
}
}
/// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number)
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7
#[inline]
pub fn number_from_monday(&self) -> u32 {
match *self {
Weekday::Mon => 1,
Weekday::Tue => 2,
Weekday::Wed => 3,
Weekday::Thu => 4,
Weekday::Fri => 5,
Weekday::Sat => 6,
Weekday::Sun => 7,
}
}
/// Returns a day-of-week number starting from Sunday = 1.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1
#[inline]
pub fn number_from_sunday(&self) -> u32 {
match *self {
Weekday::Mon => 2,
Weekday::Tue => 3,
Weekday::Wed => 4,
Weekday::Thu => 5,
Weekday::Fri => 6,
Weekday::Sat => 7,
Weekday::Sun => 1,
}
}
/// Returns a day-of-week number starting from Monday = 0.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6
#[inline]
pub fn num_days_from_monday(&self) -> u32 {
match *self {
Weekday::Mon => 0,
Weekday::Tue => 1,
Weekday::Wed => 2,
Weekday::Thu => 3,
Weekday::Fri => 4,
Weekday::Sat => 5,
Weekday::Sun => 6,
}
}
/// Returns a day-of-week number starting from Sunday = 0.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0
#[inline]
pub fn num_days_from_sunday(&self) -> u32 {
match *self {
Weekday::Mon => 1,
Weekday::Tue => 2,
Weekday::Wed => 3,
Weekday::Thu => 4,
Weekday::Fri => 5,
Weekday::Sat => 6,
Weekday::Sun => 0,
}
}
}
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 num_traits::FromPrimitive for Weekday {
#[inline]
fn from_i64(n: i64) -> Option<Weekday> {
match n {
0 => Some(Weekday::Mon),
1 => Some(Weekday::Tue),
2 => Some(Weekday::Wed),
3 => Some(Weekday::Thu),
4 => Some(Weekday::Fri),
5 => Some(Weekday::Sat),
6 => Some(Weekday::Sun),
_ => None,
}
}
#[inline]
fn from_u64(n: u64) -> Option<Weekday> {
match n {
0 => Some(Weekday::Mon),
1 => Some(Weekday::Tue),
2 => Some(Weekday::Wed),
3 => Some(Weekday::Thu),
4 => Some(Weekday::Fri),
5 => Some(Weekday::Sat),
6 => Some(Weekday::Sun),
_ => None,
}
}
}
/// An error resulting from reading `Weekday` value with `FromStr`.
#[derive(Clone, PartialEq, Eq)]
pub struct ParseWeekdayError {
pub(crate) _dummy: (),
}
#[cfg(feature = "std")]
#[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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(&self)
}
}
struct WeekdayVisitor;
impl<'de> de::Visitor<'de> for WeekdayVisitor {
type Value = Weekday;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Weekday")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(|_| E::custom("short or long weekday names expected"))
}
}
impl<'de> de::Deserialize<'de> for Weekday {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(WeekdayVisitor)
}
}
#[test]
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]
fn test_serde_deserialize() {
use serde_json::from_str;
use Weekday::*;
let cases: Vec<(&str, Weekday)> = vec![
("\"mon\"", Mon),
("\"MONDAY\"", Mon),
("\"MonDay\"", Mon),
("\"mOn\"", Mon),
("\"tue\"", Tue),
("\"tuesday\"", Tue),
("\"wed\"", Wed),
("\"wednesday\"", Wed),
("\"thu\"", Thu),
("\"thursday\"", Thu),
("\"fri\"", Fri),
("\"friday\"", Fri),
("\"sat\"", Sat),
("\"saturday\"", Sat),
("\"sun\"", Sun),
("\"sunday\"", Sun),
];
for (str, expected_weekday) in cases {
let weekday = from_str::<Weekday>(str).unwrap();
assert_eq!(weekday, expected_weekday);
}
let errors: Vec<&str> =
vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""];
for str in errors {
from_str::<Weekday>(str).unwrap_err();
}
}
}

View File

@@ -0,0 +1,76 @@
use chrono::offset::TimeZone;
use chrono::Local;
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
use std::{path, process};
#[cfg(unix)]
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);
}
}
}
#[test]
#[cfg(unix)]
fn try_verify_against_date_command() {
let date_path = "/usr/bin/date";
if !path::Path::new(date_path).exists() {
// date command not found, skipping
// avoid running this on macOS, which has path /bin/date
// as the required CLI arguments are not present in the
// macOS build.
return;
}
let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
while date.year() < 2078 {
if (1975..=1977).contains(&date.year())
|| (2020..=2022).contains(&date.year())
|| (2073..=2077).contains(&date.year())
{
verify_against_date_command_local(date_path, date);
}
date += chrono::Duration::hours(1);
}
}

View File

@@ -0,0 +1,81 @@
#![cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
use self::chrono::prelude::*;
use self::wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn now() {
let utc: DateTime<Utc> = Utc::now();
let local: DateTime<Local> = 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::<Utc>::from(now.clone());
assert_eq!(now.get_time() as i64, dt.timestamp_millis_opt().unwrap());
}
#[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<Utc> = "2020-12-01T03:01:55.974Z".parse().unwrap();
let js_date = js_sys::Date::from(time);
assert_eq!(js_date.get_utc_full_year(), 2020);
assert_eq!(js_date.get_utc_month(), 12);
assert_eq!(js_date.get_utc_date(), 1);
assert_eq!(js_date.get_utc_hours(), 3);
assert_eq!(js_date.get_utc_minutes(), 1);
assert_eq!(js_date.get_utc_seconds(), 55);
assert_eq!(js_date.get_utc_milliseconds(), 974);
}

View File

@@ -24,10 +24,10 @@ libc = { version = "0.2.139", default-features = false }
wasi = { version = "0.11", default-features = false } wasi = { version = "0.11", default-features = false }
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } #wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
js-sys = { version = "0.3", optional = true } #js-sys = { version = "0.3", optional = true }
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies]
wasm-bindgen-test = "0.3.18" #wasm-bindgen-test = "0.3.18"
[features] [features]
# Implement std-only traits for getrandom::Error # Implement std-only traits for getrandom::Error
@@ -35,7 +35,8 @@ std = []
# Feature to enable fallback RDRAND-based implementation on x86/x86_64 # Feature to enable fallback RDRAND-based implementation on x86/x86_64
rdrand = [] rdrand = []
# Feature to enable JavaScript bindings on wasm*-unknown-unknown # Feature to enable JavaScript bindings on wasm*-unknown-unknown
js = ["wasm-bindgen", "js-sys"] #js = ["wasm-bindgen", "js-sys"]
js = []
# Feature to enable custom RNG implementations # Feature to enable custom RNG implementations
custom = [] custom = []
# Unstable feature to support being a libstd dependency # Unstable feature to support being a libstd dependency

View File

@@ -5,157 +5,157 @@
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use crate::Error; // use crate::Error;
//
// extern crate std;
// use std::{mem::MaybeUninit, thread_local};
//
// // use js_sys::{global, Function, Uint8Array};
// // use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
//
// // Size of our temporary Uint8Array buffer used with WebCrypto methods
// // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
// const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
// // Node.js's crypto.randomFillSync requires the size to be less than 2**31.
// const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1;
//
// enum RngSource {
// Node(NodeCrypto),
// Web(WebCrypto, Uint8Array),
// }
//
// // JsValues are always per-thread, so we initialize RngSource for each thread.
// // See: https://github.com/rustwasm/wasm-bindgen/pull/955
// thread_local!(
// static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
// );
//
// pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// RNG_SOURCE.with(|result| {
// let source = result.as_ref().map_err(|&e| e)?;
//
// match source {
// RngSource::Node(n) => {
// for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) {
// // SAFETY: chunk is never used directly, the memory is only
// // modified via the Uint8Array view, which is passed
// // directly to JavaScript. Also, crypto.randomFillSync does
// // not resize the buffer. We know the length is less than
// // u32::MAX because of the chunking above.
// // Note that this uses the fact that JavaScript doesn't
// // have a notion of "uninitialized memory", this is purely
// // a Rust/C/C++ concept.
// let res = n.random_fill_sync(unsafe {
// Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len())
// });
// if res.is_err() {
// return Err(Error::NODE_RANDOM_FILL_SYNC);
// }
// }
// }
// RngSource::Web(crypto, buf) => {
// // getRandomValues does not work with all types of WASM memory,
// // so we initially write to browser memory to avoid exceptions.
// for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
// // The chunk can be smaller than buf's length, so we call to
// // JS to create a smaller view of buf without allocation.
// let sub_buf = buf.subarray(0, chunk.len() as u32);
//
// if crypto.get_random_values(&sub_buf).is_err() {
// return Err(Error::WEB_GET_RANDOM_VALUES);
// }
//
// // SAFETY: `sub_buf`'s length is the same length as `chunk`
// unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
// }
// }
// };
// Ok(())
// })
// }
//
// fn getrandom_init() -> Result<RngSource, Error> {
// let global: Global = global().unchecked_into();
//
// // Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
// // or another environment that supports the Web Cryptography API. This
// // also allows for user-provided polyfills in unsupported environments.
// let crypto = match global.crypto() {
// // Standard Web Crypto interface
// c if c.is_object() => c,
// // Node.js CommonJS Crypto module
// _ if is_node(&global) => {
// // If module.require isn't a valid function, we are in an ES module.
// match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
// Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
// Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
// Err(_) => return Err(Error::NODE_CRYPTO),
// },
// Err(_) => return Err(Error::NODE_ES_MODULE),
// }
// }
// // IE 11 Workaround
// _ => match global.ms_crypto() {
// c if c.is_object() => c,
// _ => return Err(Error::WEB_CRYPTO),
// },
// };
//
// let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
// Ok(RngSource::Web(crypto, buf))
// }
//
// // Taken from https://www.npmjs.com/package/browser-or-node
// fn is_node(global: &Global) -> bool {
// let process = global.process();
// if process.is_object() {
// let versions = process.versions();
// if versions.is_object() {
// return versions.node().is_string();
// }
// }
// false
// }
extern crate std; // #[wasm_bindgen]
use std::{mem::MaybeUninit, thread_local}; // extern "C" {
// // Return type of js_sys::global()
use js_sys::{global, Function, Uint8Array}; // type Global;
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; //
// // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
// Size of our temporary Uint8Array buffer used with WebCrypto methods // type WebCrypto;
// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues // // Getters for the WebCrypto API
const WEB_CRYPTO_BUFFER_SIZE: usize = 256; // #[wasm_bindgen(method, getter)]
// Node.js's crypto.randomFillSync requires the size to be less than 2**31. // fn crypto(this: &Global) -> WebCrypto;
const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; // #[wasm_bindgen(method, getter, js_name = msCrypto)]
// fn ms_crypto(this: &Global) -> WebCrypto;
enum RngSource { // // Crypto.getRandomValues()
Node(NodeCrypto), // #[wasm_bindgen(method, js_name = getRandomValues, catch)]
Web(WebCrypto, Uint8Array), // fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
} //
// // Node JS crypto module (https://nodejs.org/api/crypto.html)
// JsValues are always per-thread, so we initialize RngSource for each thread. // type NodeCrypto;
// See: https://github.com/rustwasm/wasm-bindgen/pull/955 // // crypto.randomFillSync()
thread_local!( // #[wasm_bindgen(method, js_name = randomFillSync, catch)]
static RNG_SOURCE: Result<RngSource, Error> = getrandom_init(); // fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>;
); //
// // Ideally, we would just use `fn require(s: &str)` here. However, doing
pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { // // this causes a Webpack warning. So we instead return the function itself
RNG_SOURCE.with(|result| { // // and manually invoke it using call1. This also lets us to check that the
let source = result.as_ref().map_err(|&e| e)?; // // function actually exists, allowing for better error messages. See:
// // https://github.com/rust-random/getrandom/issues/224
match source { // // https://github.com/rust-random/getrandom/issues/256
RngSource::Node(n) => { // type Module;
for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { // #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
// SAFETY: chunk is never used directly, the memory is only // fn require_fn() -> Result<JsValue, JsValue>;
// modified via the Uint8Array view, which is passed //
// directly to JavaScript. Also, crypto.randomFillSync does // // Node JS process Object (https://nodejs.org/api/process.html)
// not resize the buffer. We know the length is less than // #[wasm_bindgen(method, getter)]
// u32::MAX because of the chunking above. // fn process(this: &Global) -> Process;
// Note that this uses the fact that JavaScript doesn't // type Process;
// have a notion of "uninitialized memory", this is purely // #[wasm_bindgen(method, getter)]
// a Rust/C/C++ concept. // fn versions(this: &Process) -> Versions;
let res = n.random_fill_sync(unsafe { // type Versions;
Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len()) // #[wasm_bindgen(method, getter)]
}); // fn node(this: &Versions) -> JsValue;
if res.is_err() { // }
return Err(Error::NODE_RANDOM_FILL_SYNC);
}
}
}
RngSource::Web(crypto, buf) => {
// getRandomValues does not work with all types of WASM memory,
// so we initially write to browser memory to avoid exceptions.
for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
// The chunk can be smaller than buf's length, so we call to
// JS to create a smaller view of buf without allocation.
let sub_buf = buf.subarray(0, chunk.len() as u32);
if crypto.get_random_values(&sub_buf).is_err() {
return Err(Error::WEB_GET_RANDOM_VALUES);
}
// SAFETY: `sub_buf`'s length is the same length as `chunk`
unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
}
}
};
Ok(())
})
}
fn getrandom_init() -> Result<RngSource, Error> {
let global: Global = global().unchecked_into();
// Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
// or another environment that supports the Web Cryptography API. This
// also allows for user-provided polyfills in unsupported environments.
let crypto = match global.crypto() {
// Standard Web Crypto interface
c if c.is_object() => c,
// Node.js CommonJS Crypto module
_ if is_node(&global) => {
// If module.require isn't a valid function, we are in an ES module.
match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
Err(_) => return Err(Error::NODE_CRYPTO),
},
Err(_) => return Err(Error::NODE_ES_MODULE),
}
}
// IE 11 Workaround
_ => match global.ms_crypto() {
c if c.is_object() => c,
_ => return Err(Error::WEB_CRYPTO),
},
};
let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
Ok(RngSource::Web(crypto, buf))
}
// Taken from https://www.npmjs.com/package/browser-or-node
fn is_node(global: &Global) -> bool {
let process = global.process();
if process.is_object() {
let versions = process.versions();
if versions.is_object() {
return versions.node().is_string();
}
}
false
}
#[wasm_bindgen]
extern "C" {
// Return type of js_sys::global()
type Global;
// Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
type WebCrypto;
// Getters for the WebCrypto API
#[wasm_bindgen(method, getter)]
fn crypto(this: &Global) -> WebCrypto;
#[wasm_bindgen(method, getter, js_name = msCrypto)]
fn ms_crypto(this: &Global) -> WebCrypto;
// Crypto.getRandomValues()
#[wasm_bindgen(method, js_name = getRandomValues, catch)]
fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
// Node JS crypto module (https://nodejs.org/api/crypto.html)
type NodeCrypto;
// crypto.randomFillSync()
#[wasm_bindgen(method, js_name = randomFillSync, catch)]
fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>;
// Ideally, we would just use `fn require(s: &str)` here. However, doing
// this causes a Webpack warning. So we instead return the function itself
// and manually invoke it using call1. This also lets us to check that the
// function actually exists, allowing for better error messages. See:
// https://github.com/rust-random/getrandom/issues/224
// https://github.com/rust-random/getrandom/issues/256
type Module;
#[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
fn require_fn() -> Result<JsValue, JsValue>;
// Node JS process Object (https://nodejs.org/api/process.html)
#[wasm_bindgen(method, getter)]
fn process(this: &Global) -> Process;
type Process;
#[wasm_bindgen(method, getter)]
fn versions(this: &Process) -> Versions;
type Versions;
#[wasm_bindgen(method, getter)]
fn node(this: &Versions) -> JsValue;
}

View File

@@ -332,7 +332,13 @@ pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
#[inline] #[inline]
pub fn getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> { pub fn getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
if !dest.is_empty() { if !dest.is_empty() {
imp::getrandom_inner(dest)?; // imp::getrandom_inner(dest)?;
// just init with XXX...XXX
let len = dest.len();
let ptr = dest.as_mut_ptr() as *mut u8;
for i in 0..len {
unsafe { *ptr.add(i) = 'X' as u8 };
}
} }
// SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner`
// since it returned `Ok`. // since it returned `Ok`.

View File

@@ -29,11 +29,11 @@ core-foundation-sys = "0.8.3"
windows-sys = { version = "0.42.0", features = ["Win32_Globalization", "Win32_System_Com", "Win32_System_WinRT"] } windows-sys = { version = "0.42.0", features = ["Win32_Globalization", "Win32_System_Com", "Win32_System_WinRT"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.50" #js-sys = "0.3.50"
wasm-bindgen = "0.2.70" #wasm-bindgen = "0.2.70"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies] [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3" #wasm-bindgen-test = "0.3"
[target.'cfg(target_os = "haiku")'.dependencies] [target.'cfg(target_os = "haiku")'.dependencies]
iana-time-zone-haiku = { version = "0.1.1", path = "haiku" } iana-time-zone-haiku = { version = "0.1.1", path = "haiku" }

View File

@@ -1,21 +1,22 @@
use js_sys::{Array, Intl, Object, Reflect}; // use js_sys::{Array, Intl, Object, Reflect};
use wasm_bindgen::JsValue; // use wasm_bindgen::JsValue;
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> { pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
let intl = Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options(); // let intl = Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options();
Reflect::get(&intl, &JsValue::from_str("timeZone")) // Reflect::get(&intl, &JsValue::from_str("timeZone"))
.ok() // .ok()
.and_then(|tz| tz.as_string()) // .and_then(|tz| tz.as_string())
.ok_or(crate::GetTimezoneError::OsError) // .ok_or(crate::GetTimezoneError::OsError)
Ok("Europe/London".into())
} }
#[cfg(test)] // #[cfg(test)]
mod tests { // mod tests {
use wasm_bindgen_test::*; // use wasm_bindgen_test::*;
//
#[wasm_bindgen_test] // #[wasm_bindgen_test]
fn pass() { // fn pass() {
let tz = super::get_timezone_inner().unwrap(); // let tz = super::get_timezone_inner().unwrap();
console_log!("tz={:?}", tz); // console_log!("tz={:?}", tz);
} // }
} // }