Bug 1951517 - Update Rust chrono crate to 0.4.40. r=glandium,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D245369
This commit is contained in:
Mark Hammond
2025-04-18 15:57:39 +00:00
parent b05b4c808c
commit 016985f33c
119 changed files with 35205 additions and 16509 deletions

49
Cargo.lock generated
View File

@@ -54,6 +54,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_log-sys"
version = "0.2.0"
@@ -782,16 +788,17 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.19"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"libc",
"num-integer",
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"time 0.1.45",
"winapi",
"wasm-bindgen",
"windows-link",
]
[[package]]
@@ -2973,6 +2980,30 @@ dependencies = [
"want",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_calendar"
version = "1.5.2"
@@ -7655,6 +7686,12 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.2.0"

View File

@@ -646,6 +646,12 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.7.0 -> 0.8.1"
[[audits.android-tzdata]]
who = "Mark Hammond <mhammond@skippinet.com.au>"
criteria = "safe-to-deploy"
version = "0.1.1"
notes = "Small crate parsing a file. No unsafe code"
[[audits.android_logger]]
who = "Jan-Erik Rediger <jrediger@mozilla.com>"
criteria = "safe-to-deploy"
@@ -1159,6 +1165,12 @@ who = "Bobby Holley <bobbyholley@gmail.com>"
criteria = "safe-to-deploy"
delta = "0.1.2 -> 0.1.2@git:ed8a4c6f900a90d4dbc1d64b856e61490a1c3570"
[[audits.chrono]]
who = "Mark Hammond <mhammond@skippinet.com.au>"
criteria = "safe-to-deploy"
delta = "0.4.19 -> 0.4.40"
notes = "Significant refactor of both implementation and dependencies."
[[audits.circular]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
@@ -2728,6 +2740,11 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-run"
delta = "0.14.23 -> 0.14.24"
[[audits.iana-time-zone]]
who = "Mark Hammond <mhammond@skippinet.com.au>"
criteria = "safe-to-deploy"
delta = "0.1.61 -> 0.1.63"
[[audits.icu_calendar]]
who = "André Bargull <andre.bargull@gmail.com>"
criteria = "safe-to-deploy"
@@ -6233,6 +6250,12 @@ criteria = "safe-to-deploy"
delta = "0.1.2 -> 0.3.1"
notes = "Maintained by me. I have written or reviewed all of the code."
[[audits.windows-link]]
who = "Mark Hammond <mhammond@skippinet.com.au>"
criteria = "safe-to-deploy"
version = "0.1.1"
notes = "A microsoft crate allowing unsafe calls to windows apis."
[[audits.winreg]]
who = "Ray Kraesig <rkraesig@mozilla.com>"
criteria = "safe-to-run"

View File

@@ -1202,6 +1202,11 @@ criteria = "safe-to-deploy"
delta = "0.4.1 -> 0.5.0"
notes = "Minor changes for a `no_std` upgrade but otherwise everything looks as expected."
[[audits.bytecode-alliance.audits.iana-time-zone-haiku]]
who = "Dan Gohman <dev@sunfishcode.online>"
criteria = "safe-to-deploy"
version = "0.1.2"
[[audits.bytecode-alliance.audits.id-arena]]
who = "Nick Fitzgerald <fitzgen@gmail.com>"
criteria = "safe-to-deploy"
@@ -1705,6 +1710,13 @@ criteria = "safe-to-run"
version = "0.14.20"
aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT"
[[audits.google.audits.iana-time-zone]]
who = "Manish Goregaokar <manishearth@google.com>"
criteria = "safe-to-deploy"
version = "0.1.61"
notes = "Some unsafe: interfacing with system timezone APIs"
aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT"
[[audits.google.audits.indexmap]]
who = "Lukasz Anforowicz <lukasza@chromium.org>"
criteria = "safe-to-deploy"

View File

@@ -0,0 +1 @@
{"files":{"Cargo.toml":"a87d9acc9827a50c7a96a88720c5dd055cbc08b1144dff95bd572ff977d4a79a","LICENSE-APACHE":"4458503dd48e88c4e0b945fb252a08b93c40ec757309b8ffa7c594dfa1e35104","LICENSE-MIT":"002c2696d92b5c8cf956c11072baa58eaf9f6ade995c031ea635c6a1ee342ad1","README.md":"6dfe0c602dc61eebe118900ed66a2c1f7887b9fe95b36e1c2974c4e8fa7ebd4b","src/lib.rs":"8f421233df83f82e737930ca8a2ad254966334183148bcc170f9c405df230de2","src/tzdata.rs":"78920925b04219910511e9a1f036f468cd2925c0054f280d6a00b106529046e7"},"package":"e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"}

View File

@@ -0,0 +1,34 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "android-tzdata"
version = "0.1.1"
authors = ["RumovZ"]
include = [
"src/**/*",
"LICENSE-*",
"README.md",
]
description = "Parser for the Android-specific tzdata file"
readme = "README.md"
keywords = [
"parser",
"android",
"timezone",
]
categories = ["date-and-time"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/RumovZ/android-tzdata"
[dev-dependencies.zip]
version = "0.6.4"

View File

@@ -0,0 +1,201 @@
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,21 @@
MIT License
Copyright (c) [year] [fullname]
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.

View File

@@ -0,0 +1,20 @@
# android-tzdata
Parser for the Android-specific tzdata file.
## License
Licensed under either of
- Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license
([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

View File

@@ -0,0 +1,29 @@
//! Parser for the Android-specific tzdata file.
mod tzdata;
/// Tries to locate the `tzdata` file, parse it, and return the entry for the
/// requested time zone.
///
/// # Errors
///
/// Returns an [std::io::Error] if the `tzdata` file cannot be found and parsed, or
/// if it does not contain the requested timezone entry.
///
/// # Example
///
/// ```rust
/// # use std::error::Error;
/// # use android_tzdata::find_tz_data;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let tz_data = find_tz_data("Europe/Kiev")?;
/// // Check it's version 2 of the [Time Zone Information Format](https://www.ietf.org/archive/id/draft-murchison-rfc8536bis-02.html).
/// assert!(tz_data.starts_with(b"TZif2"));
/// # Ok(())
/// # }
/// ```
pub fn find_tz_data(tz_name: impl AsRef<str>) -> Result<Vec<u8>, std::io::Error> {
let mut file = tzdata::find_file()?;
tzdata::find_tz_data_in_file(&mut file, tz_name.as_ref())
}

View File

@@ -0,0 +1,166 @@
//! Logic was mainly ported from https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/util/ZoneInfoDB.java
use core::{cmp::Ordering, convert::TryInto};
use std::{
fs::File,
io::{self, ErrorKind, Read, Seek, SeekFrom},
};
// The database uses 32-bit (4 byte) integers.
const TZ_INT_SIZE: usize = 4;
// The first 12 bytes contain a special version string.
const MAGIC_SIZE: usize = 12;
const HEADER_SIZE: usize = MAGIC_SIZE + 3 * TZ_INT_SIZE;
// The database reserves 40 bytes for each id.
const TZ_NAME_SIZE: usize = 40;
const INDEX_ENTRY_SIZE: usize = TZ_NAME_SIZE + 3 * TZ_INT_SIZE;
const TZDATA_LOCATIONS: [TzdataLocation; 2] = [
TzdataLocation {
env_var: "ANDROID_DATA",
path: "/misc/zoneinfo/",
},
TzdataLocation {
env_var: "ANDROID_ROOT",
path: "/usr/share/zoneinfo/",
},
];
#[derive(Debug)]
struct TzdataLocation {
env_var: &'static str,
path: &'static str,
}
#[derive(Debug, Clone, Copy)]
struct Header {
index_offset: usize,
data_offset: usize,
_zonetab_offset: usize,
}
#[derive(Debug)]
struct Index(Vec<u8>);
#[derive(Debug, Clone, Copy)]
struct IndexEntry<'a> {
_name: &'a [u8],
offset: usize,
length: usize,
_raw_utc_offset: usize,
}
pub(super) fn find_file() -> Result<File, io::Error> {
for location in &TZDATA_LOCATIONS {
if let Ok(env_value) = std::env::var(location.env_var) {
if let Ok(file) = File::open(format!("{}{}tzdata", env_value, location.path)) {
return Ok(file);
}
}
}
Err(io::Error::from(io::ErrorKind::NotFound))
}
pub(super) fn find_tz_data_in_file(
mut file: impl Read + Seek,
tz_name: &str,
) -> Result<Vec<u8>, io::Error> {
let header = Header::new(&mut file)?;
let index = Index::new(&mut file, header)?;
if let Some(entry) = index.find_entry(tz_name) {
file.seek(SeekFrom::Start((entry.offset + header.data_offset) as u64))?;
let mut tz_data = vec![0u8; entry.length];
file.read_exact(&mut tz_data)?;
Ok(tz_data)
} else {
Err(io::Error::from(ErrorKind::NotFound))
}
}
impl Header {
fn new(mut file: impl Read + Seek) -> Result<Self, io::Error> {
let mut buf = [0; HEADER_SIZE];
file.read_exact(&mut buf)?;
if !buf.starts_with(b"tzdata") || buf[MAGIC_SIZE - 1] != 0u8 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid magic number",
));
}
Ok(Self {
index_offset: parse_tz_int(&buf, MAGIC_SIZE) as usize,
data_offset: parse_tz_int(&buf, MAGIC_SIZE + TZ_INT_SIZE) as usize,
_zonetab_offset: parse_tz_int(&buf, MAGIC_SIZE + 2 * TZ_INT_SIZE) as usize,
})
}
}
impl Index {
fn new(mut file: impl Read + Seek, header: Header) -> Result<Self, io::Error> {
file.seek(SeekFrom::Start(header.index_offset as u64))?;
let size = header.data_offset - header.index_offset;
let mut bytes = vec![0; size];
file.read_exact(&mut bytes)?;
Ok(Self(bytes))
}
fn find_entry(&self, name: &str) -> Option<IndexEntry> {
let name_bytes = name.as_bytes();
let name_len = name_bytes.len();
if name_len > TZ_NAME_SIZE {
return None;
}
let zeros = [0u8; TZ_NAME_SIZE];
let cmp = |chunk: &&[u8]| -> Ordering {
// tz names always have TZ_NAME_SIZE bytes and are right-padded with 0s
// so we check that a chunk starts with `name` and the remaining bytes are 0
chunk[..name_len]
.cmp(name_bytes)
.then_with(|| chunk[name_len..TZ_NAME_SIZE].cmp(&zeros[name_len..]))
};
let chunks: Vec<_> = self.0.chunks_exact(INDEX_ENTRY_SIZE).collect();
chunks
.binary_search_by(cmp)
.map(|idx| IndexEntry::new(chunks[idx]))
.ok()
}
}
impl<'a> IndexEntry<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self {
_name: bytes[..TZ_NAME_SIZE]
.splitn(2, |&b| b == 0u8)
.next()
.unwrap(),
offset: parse_tz_int(bytes, TZ_NAME_SIZE) as usize,
length: parse_tz_int(bytes, TZ_NAME_SIZE + TZ_INT_SIZE) as usize,
_raw_utc_offset: parse_tz_int(bytes, TZ_NAME_SIZE + 2 * TZ_INT_SIZE) as usize,
}
}
}
/// Panics if slice does not contain [TZ_INT_SIZE] bytes beginning at start.
fn parse_tz_int(slice: &[u8], start: usize) -> u32 {
u32::from_be_bytes(slice[start..start + TZ_INT_SIZE].try_into().unwrap())
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::io::Cursor;
#[test]
fn parse() {
let mut archive = File::open("tests/resources/tzdata.zip").unwrap();
let mut zip = zip::ZipArchive::new(&mut archive).unwrap();
let mut file = zip.by_index(0).unwrap();
let mut data = Vec::new();
file.read_to_end(&mut data).unwrap();
let cursor = Cursor::new(data);
let tz = find_tz_data_in_file(cursor, "Europe/Kiev").unwrap();
assert!(tz.starts_with(b"TZif2"));
}
}

View File

@@ -1 +1 @@
{"files":{"AUTHORS.txt":"bcca43c486176e81edcb64d065d418986cb5ca71af7ee4a648236a644305960d","CHANGELOG.md":"a925f71022f1c9e34e5df7c016ab2a32e9be22fc503929aa87d567900ebe064b","Cargo.toml":"13fab9c53580e2754c4fa075768ee9591e8d4b4a437ac9dbae503c4d4b6daf09","LICENSE.txt":"46610329ff0b38effb9cb05979ff1ef761e465fed96b2eaca39e439d00129fd7","README.md":"39985917c9bc71ee87a24808369ddfab22f3c6343da7c87c2274c965e1eec9db","benches/chrono.rs":"4de07b4c7bc907926e5a6ed20fc00a30e4e0491604f3d78eece81f1a8b45870a","benches/serde.rs":"e1a9624bcad4892c4cc7b76d5ad14607a016305a27b2231c2c77814b1d4448a8","rustfmt.toml":"f74204a6f92aa7422a16ecb2ffe2d5bae0f123b778d08b5db1a398a3c9ca4306","src/date.rs":"ec234e777efa9d8cd2f0c7f87db9296eda04bd4d56c994232a3329eb863acd34","src/datetime.rs":"63e582cd17f3070cbcb586bea15b5102ead1a898252e14b1817a498750043102","src/div.rs":"6c0a08648bb688b418b42f1c7be8387057a9db9774d3f09e97df2a183b79cd9f","src/format/locales.rs":"8c14cb93c68b747947b74ab2933aae53c8a0edd009ff16815e756015fbea6e9f","src/format/mod.rs":"09c66dee24e69325ce9a7be5b6c830317515ee37f7c2a26f48835484ed155c89","src/format/parse.rs":"c98217756370c6186bdab117373912208c018a2c6411f9be0fd1aecab54fb10c","src/format/parsed.rs":"6d4453a5fedc753fae268e0f545fcc817d48f9ce6803231d1c83aeb1f626c138","src/format/scan.rs":"1846554a45c776d164239ec6874881c6c97468631a1c00c8c956630420ea408f","src/format/strftime.rs":"8bbe43ca06c8a5e71187431890a54ff1faeb3be990c0d9d8c2fd8ee8b5c1361f","src/lib.rs":"522163d278acefa80fd1af4afeec2fdd2648e13e3fbdde8e6d31214253b013f9","src/naive/date.rs":"8be6146b3c15c395a71d30449a799b6e4387d1cd515e7e849509b824161fa6e3","src/naive/datetime.rs":"fd0de90d13793e5a8ecf99f63fa27592a6add57ba39a116495f9b2b253323fce","src/naive/internals.rs":"8c1aa5ab3373e04b51d36ed1591d667e1450055208b8c44d3fb720a496722c57","src/naive/isoweek.rs":"a8c5ae43ee1b916a2f79d42230aea448bf85691d615c4d271fcf1809e763df01","src/naive/time.rs":"b83e4ae0a809badce9131a19e3c5c75dbb823db4ef2f79d50bd522126ba0b48f","src/offset/fixed.rs":"19b97271300b821407756e14f64a42d48eb25a71c7011b2967885e4946e089ef","src/offset/local.rs":"9bc3af0ebf35a49858647302ac7e44abe344cbb76819d273311e50d234d1e6b2","src/offset/mod.rs":"a1036f75fc686603b216f9bb45b1689c8b34198b617b204800ceafb87b66ec45","src/offset/utc.rs":"2940ade0e834a9e1247600e92d38ee7bb11653f2d267862f99f292f617e25ca4","src/oldtime.rs":"780bc4ae5652affa8f7020580bb5977e9f32b304b0905c742124fd87a82ae50e","src/round.rs":"f7ae453ec0caacffc23bc0bad38b2c59b616c097ccaa0a15c0e7bcb8d1e1aed3","src/sys.rs":"4a3e8a96a2060e7df82c57405a5de4fffa54132a77fdd919970d51f3c43442cb","src/sys/stub.rs":"78babcdbe867ce5978bd69935e141ee15313ee7d90edce52355a19ab906a019b","src/sys/unix.rs":"c838ba088423c2b26643bd7191fe1914f099c14d632c9874f236c9c2c9dbd2d6","src/sys/windows.rs":"5c19383e9ffe11c16102008b5984dbd3455fd4003df15ac1565720c0a5edfac8","tests/wasm.rs":"d152681d5a79d9bbb69684433388bb8d7ca90e181387d4690cf5bda8cf25d17f"},"package":"670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"}
{"files":{"CITATION.cff":"6eb81794e04ee1d4bb56dd2767a2d5c6e2200f35f5d976437d7e299059494c66","Cargo.lock":"2b2fabd7ad614c4e3f0cbeaa2de479bd489ce477b7a28640d5b6bb5ef5038557","Cargo.toml":"41cec533b67eba086cd152582c428f2cd5baa5f29b0a5783d4c2f7ea4978190e","LICENSE.txt":"a3d4e565d73d108b07074757733e1dcd3ee20617350a135e1122935881311d50","src/date.rs":"10f03590ccab03c3efc076ef55bf218f1e93fdd5f85e4440f46b7590373619b1","src/datetime/mod.rs":"be0e61a845a465fda353fb579948087d9aadad6543313071fd8740f7a2c1f384","src/datetime/serde.rs":"3b9fbeb0b7f269d3162aeb7373e9829b58690603860ec5e23cdebb63a06efca2","src/datetime/tests.rs":"602dfd5b78c3aa7e6b30b0567f070e08d485649ce02bb40a511d1710f7301e63","src/format/formatting.rs":"0a3286b78a0c0b08adedbbd06c1894ce3ace1cf919db80cfe3ba603f7604aaeb","src/format/locales.rs":"d49de3da43941c94b1108f8fe84915cc5e1c13a1f1c84fd7356f9b7c61f19242","src/format/mod.rs":"df752a29ae2293fa71d75374b070925b1874198cc6ddbf5bcd717b26457bf44a","src/format/parse.rs":"b9d57bf8174b453864ed373d1ebb328202c0f67bd727a4270f430c088c311017","src/format/parsed.rs":"8d653af39d192a4c5234e36f72880757c9ff55ff55e979ec23c665b2fd6f34a5","src/format/scan.rs":"687117a94f7c825a8dd9f6462f9a6dbc400adaf13774d0fb8cdf025598d21cd2","src/format/strftime.rs":"066b8bea999aa69e760c05c17b683a0a411612b28a5bbf645eb8f9307fc708e4","src/lib.rs":"ea193c5d2214424137ffeb42fde6024f6003ed952525bcea331f1700bd6fad64","src/month.rs":"0f174e6b0047a1138f1713e596889097228eed89e267aa744bfecdd0a1efc5e4","src/naive/date/mod.rs":"4076847fa7926dfd70843de2f0322790354cb1cd4c9fd2e62455bacc4f00b318","src/naive/date/tests.rs":"d90f09052d4b4bbab760450d37b19ca698153a10e27249d05f3c11dc297a3e62","src/naive/datetime/mod.rs":"1465f96a1ed702e1080bc0129e84d435960987039f6425b9f7d09c0dcb8c2241","src/naive/datetime/serde.rs":"eeb8e5a6ea9632799673a6f9e4b72de0ae606ada29b20cbc1b1dbb0f9312b41c","src/naive/datetime/tests.rs":"f890c8ba06a0426d823ec4ee538d84fe38ccba6e3bd1503442cca9853186d3e5","src/naive/internals.rs":"6e2da15f601a1d15742e2ac8eaf0e23aaf61a3c92480c749c86c460a56907701","src/naive/isoweek.rs":"5c253901e2ee50316839e5f2490c59f5ca3c714d7523e01406cbdfc4e5bc6e7e","src/naive/mod.rs":"3d13c4e36a4d9f7a759fd2edf11c6e9e0b54c0c4380a671281964fe3e0315101","src/naive/time/mod.rs":"305c105fb5837b12f773aa6b90e9973bfe90895a97d0d2a20a67c704cbbf64c8","src/naive/time/serde.rs":"6c260c6d0c45dec113f3dcff6056450c524511c37f7c30da9324ad80aff2288b","src/naive/time/tests.rs":"70143375785969ed348fcc1ceab50b670d239209191b938823dd7b25a97ced40","src/offset/fixed.rs":"cfd1c9d6ffedb9dd219e26b966e5bdd4fa52d24f3fdb598142822cb6f8b51388","src/offset/local/mod.rs":"d27850947cfb649ada0bce052136e01e27035f06f8753adcb4f7bfc0c3332e40","src/offset/local/tz_info/mod.rs":"c8096637c04687ea396e444c25fdf0008e2b9975ab3c173a41dd63ac213c7877","src/offset/local/tz_info/parser.rs":"ec21d8739a86fb4e77551733e13af9964fbc01f80c87d7a164f6185ca9928c22","src/offset/local/tz_info/rule.rs":"bfc9e6257aeaaa23edef69b00acee58233846702eb969b8011d1b2425d15d10c","src/offset/local/tz_info/timezone.rs":"91863819f1b17d73682f62a46d7420a3390088075f0c93577e42e048fafe5a6f","src/offset/local/unix.rs":"3d701d5d37be23b90ac97d16832553ff6c458e98d1e293616528d85885db8c57","src/offset/local/win_bindings.rs":"a7e705048f99c594fcb5eca2ed619b9ceec6ac4f00d4bf21dc132d0a8e079281","src/offset/local/win_bindings.txt":"d680de1e8fd07d435d20143877dcb3d719d72e24ff7fe5baae97e956c24f7fed","src/offset/local/windows.rs":"e9909f06e84c6334433fe24e1db14ca61c116889df9db6903917a3083fdd4606","src/offset/mod.rs":"ab1021bd280e921c46fa28ae64e1ba5a30522a5f1dcdc5ebfe384c1c8a721f75","src/offset/utc.rs":"4b5449427cebf609cb5bbcc07ae70faeb3eab4f742d5a6a521e9118227d6eb2d","src/round.rs":"81f4cb707e723256429daf7a15d8d4f510f6f6baa892dc220fa2862da7f24e08","src/time_delta.rs":"b62f63d0ed22a55b2dab53fce16cc36db4c8b73ad0f4fb2fdc536b970c89ea27","src/traits.rs":"14ecbb8c4faaef20aea69a24f4df61d472d87771fef57ae990b10bf950a46315","src/weekday.rs":"6f8374bf39300fbf08ce6b3f64247afb29b2da9d60b5ff78f72b201b05272305","tests/dateutils.rs":"905e0f9f8d07f7cbee1b1a36369a1402e4cf62370b52dda1557c563b7a0de758","tests/wasm.rs":"252f16aeeaacbf26ca268b9a5e53aee3560cd1f071142a7a16d1509a02758a17","tests/win_bindings.rs":"62206936e3bd2ae1cc889c45d65d6182f553dc6c2b29c83cb0d897b2406a9040"},"package":"1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"}

View File

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

View File

@@ -1,740 +0,0 @@
ChangeLog for Chrono
====================
This documents all notable changes to [Chrono](https://github.com/chronotope/chrono).
Chrono obeys the principle of [Semantic Versioning](http://semver.org/), with one caveat: we may
move previously-existing code behind a feature gate and put it behind a new feature. This new
feature will always be placed in the `previously-default` feature, which you can use to prevent
breakage if you use `no-default-features`.
There were/are numerous minor versions before 1.0 due to the language changes.
Versions with only mechanical changes will be omitted from the following list.
## 0.4.20 (unreleased)
## 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`.

33
third_party/rust/chrono/CITATION.cff vendored Normal file
View File

@@ -0,0 +1,33 @@
# Parser settings.
cff-version: 1.2.0
message: Please cite this crate using these information.
# Version information.
date-released: 2025-02-26
version: 0.4.40
# Project information.
abstract: Date and time library for Rust
authors:
- alias: quodlibetor
family-names: Maister
given-names: Brandon W.
- alias: djc
family-names: Ochtman
given-names: Dirkjan
- alias: lifthrasiir
family-names: Seonghoon
given-names: Kang
- alias: esheppa
family-names: Sheppard
given-names: Eric
- alias: pitdicker
family-names: Dicker
given-names: Paul
license:
- Apache-2.0
- MIT
repository-artifact: https://crates.io/crates/chrono
repository-code: https://github.com/chronotope/chrono
title: chrono
url: https://docs.rs/chrono

808
third_party/rust/chrono/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,808 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bstr"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytecheck"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
dependencies = [
"bytecheck_derive",
"ptr_meta",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "cc"
version = "1.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
dependencies = [
"android-tzdata",
"arbitrary",
"bincode",
"iana-time-zone",
"js-sys",
"num-traits",
"pure-rust-locales",
"rkyv",
"serde",
"serde_derive",
"serde_json",
"similar-asserts",
"wasm-bindgen",
"wasm-bindgen-test",
"windows-bindgen",
"windows-link",
]
[[package]]
name = "console"
version = "0.15.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "either"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minicov"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
dependencies = [
"cc",
"walkdir",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "pure-rust-locales"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a"
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
[[package]]
name = "rend"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
dependencies = [
"bytecheck",
]
[[package]]
name = "rkyv"
version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
dependencies = [
"bitvec",
"bytecheck",
"hashbrown",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
"tinyvec",
]
[[package]]
name = "rkyv_derive"
version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "serde_json"
version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
dependencies = [
"bstr",
"unicode-segmentation",
]
[[package]]
name = "similar-asserts"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a"
dependencies = [
"console",
"similar",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tinyvec"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.98",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
dependencies = [
"js-sys",
"minicov",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-bindgen"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d72f8d00d9d4bd00634be0ddbb927cec37d010b55753c4c07a401f4035da6268"
dependencies = [
"rayon",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]

View File

@@ -3,84 +3,141 @@
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.61.0"
name = "chrono"
version = "0.4.19"
authors = ["Kang Seonghoon <public+rust@mearie.org>", "Brandon W Maister <quodlibetor@gmail.com>"]
exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"]
version = "0.4.40"
build = false
include = [
"src/*",
"tests/*.rs",
"LICENSE.txt",
"CITATION.cff",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Date and time library for Rust"
homepage = "https://github.com/chronotope/chrono"
documentation = "https://docs.rs/chrono/"
readme = "README.md"
keywords = ["date", "time", "calendar"]
keywords = [
"date",
"time",
"calendar",
]
categories = ["date-and-time"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
repository = "https://github.com/chronotope/chrono"
[package.metadata.docs.rs]
features = ["serde"]
features = [
"arbitrary",
"rkyv",
"serde",
"unstable-locales",
]
rustdoc-args = [
"--cfg",
"docsrs",
]
[package.metadata.playground]
features = ["serde"]
[features]
__internal_bench = []
alloc = []
clock = [
"winapi",
"iana-time-zone",
"android-tzdata",
"now",
]
default = [
"clock",
"std",
"oldtime",
"wasmbind",
]
libc = []
now = ["std"]
oldtime = []
rkyv = [
"dep:rkyv",
"rkyv/size_32",
]
rkyv-16 = [
"dep:rkyv",
"rkyv?/size_16",
]
rkyv-32 = [
"dep:rkyv",
"rkyv?/size_32",
]
rkyv-64 = [
"dep:rkyv",
"rkyv?/size_64",
]
rkyv-validation = ["rkyv?/validation"]
std = ["alloc"]
unstable-locales = ["pure-rust-locales"]
wasmbind = [
"wasm-bindgen",
"js-sys",
]
winapi = ["windows-link"]
[lib]
name = "chrono"
path = "src/lib.rs"
[[bench]]
name = "chrono"
harness = false
required-features = ["__internal_bench"]
[[test]]
name = "dateutils"
path = "tests/dateutils.rs"
[[bench]]
name = "serde"
harness = false
required-features = ["serde"]
[dependencies.libc]
version = "0.2.69"
[[test]]
name = "wasm"
path = "tests/wasm.rs"
[[test]]
name = "win_bindings"
path = "tests/win_bindings.rs"
[dependencies.arbitrary]
version = "1.0.0"
features = ["derive"]
optional = true
[dependencies.num-integer]
version = "0.1.36"
default-features = false
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.pure-rust-locales]
version = "0.5.2"
version = "0.8"
optional = true
[dependencies.rustc-serialize]
version = "0.3.20"
[dependencies.rkyv]
version = "0.7.43"
optional = true
default-features = false
[dependencies.serde]
version = "1.0.99"
optional = true
default-features = false
[dependencies.time]
version = "0.1.43"
optional = true
[dev-dependencies.bincode]
version = "0.8.0"
[dev-dependencies.criterion]
version = "0.3"
[dev-dependencies.doc-comment]
version = "0.3"
[dev-dependencies.num-iter]
version = "0.1.35"
default-features = false
version = "1.3.0"
[dev-dependencies.serde_derive]
version = "1"
@@ -89,31 +146,32 @@ default-features = false
[dev-dependencies.serde_json]
version = "1"
[features]
__doctest = []
__internal_bench = []
alloc = []
clock = ["libc", "std", "winapi"]
default = ["clock", "std", "oldtime"]
oldtime = ["time"]
std = []
unstable-locales = ["pure-rust-locales", "alloc"]
wasmbind = ["wasm-bindgen", "js-sys"]
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys]
[dev-dependencies.similar-asserts]
version = "1.6.1"
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.js-sys]
version = "0.3"
optional = true
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.wasm-bindgen]
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.wasm-bindgen]
version = "0.2"
optional = true
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dev-dependencies.wasm-bindgen-test]
version = "0.3"
[target."cfg(windows)".dependencies.winapi]
version = "0.3.0"
features = ["std", "minwinbase", "minwindef", "timezoneapi"]
optional = true
[badges.appveyor]
repository = "chronotope/chrono"
[badges.travis-ci]
repository = "chronotope/chrono"
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies.wasm-bindgen-test]
version = "0.3"
[target.'cfg(target_os = "android")'.dependencies.android-tzdata]
version = "0.1.1"
optional = true
[target."cfg(unix)".dependencies.iana-time-zone]
version = "0.1.45"
features = ["fallback"]
optional = true
[target."cfg(windows)".dependencies.windows-link]
version = "0.1"
optional = true
[target."cfg(windows)".dev-dependencies.windows-bindgen]
version = "0.60"

View File

@@ -1,5 +1,5 @@
Rust-chrono is dual-licensed under The MIT License [1] and
Apache 2.0 License [2]. Copyright (c) 2014--2017, Kang Seonghoon and
Apache 2.0 License [2]. Copyright (c) 2014--2025, Kang Seonghoon and
contributors.
Nota Bene: This is same as the Rust Project's own license.

View File

@@ -1,419 +0,0 @@
[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/workflows/test/badge.svg
[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest
[cratesio-image]: https://img.shields.io/crates/v/chrono.svg
[cratesio]: https://crates.io/crates/chrono
[docsrs-image]: https://docs.rs/chrono/badge.svg
[docsrs]: https://docs.rs/chrono
[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg
[gitter]: https://gitter.im/chrono-rs/chrono
It aims to be a feature-complete superset of
the [time](https://github.com/rust-lang-deprecated/time) library.
In particular,
* Chrono strictly adheres to ISO 8601.
* Chrono is timezone-aware by default, with separate timezone-naive types.
* Chrono is space-optimal and (while not being the primary goal) reasonably efficient.
There were several previous attempts to bring a good date and time library to Rust,
which Chrono builds upon and should acknowledge:
* [Initial research on
the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md)
* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs)
* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime)
Any significant changes to Chrono are documented in
the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file.
## Usage
Put this in your `Cargo.toml`:
```toml
[dependencies]
chrono = "0.4"
```
### 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`), independent of whether
`std::time::SystemTime` is present, depends on having a libc.
Optional features:
- `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project
- [`serde`][]: Enable serialization/deserialization via serde.
- `unstable-locales`: Enable localization. This adds various methods with a
`_localized` suffix. The implementation and API may change or even be
removed in a patch release. Feedback welcome.
[`serde`]: https://github.com/serde-rs/serde
[wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen
See the [cargo docs][] for examples of specifying features.
[cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features
## Overview
### Duration
Chrono currently uses its own [`Duration`] type to represent the magnitude
of a time span. Since this has the same name as the newer, standard type for
duration, the reference will refer this type as `OldDuration`.
Note that this is an "accurate" duration represented as seconds and
nanoseconds and does not represent "nominal" components such as days or
months.
When the `oldtime` feature is enabled, [`Duration`] is an alias for the
[`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html)
type from v0.1 of the time crate. time v0.1 is deprecated, so new code
should disable the `oldtime` feature and use the `chrono::Duration` type
instead. The `oldtime` feature is enabled by default for backwards
compatibility, but future versions of Chrono are likely to remove the
feature entirely.
Chrono does not yet natively support
the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type,
but it will be supported in the future.
Meanwhile you can convert between two types with
[`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std)
and
[`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std)
methods.
### Date and Time
Chrono provides a
[**`DateTime`**](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html)
type to represent a date and a time in a timezone.
For more abstract moment-in-time tracking such as internal timekeeping
that is unconcerned with timezones, consider
[`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html),
which tracks your system clock, or
[`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which
is an opaque but monotonically-increasing representation of a moment in time.
`DateTime` is timezone-aware and must be constructed from
the [**`TimeZone`**](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html) object,
which defines how the local date is converted to and back from the UTC date.
There are three well-known `TimeZone` implementations:
* [**`Utc`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html) specifies the UTC time zone. It is most efficient.
* [**`Local`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html) specifies the system local time zone.
* [**`FixedOffset`**](https://docs.rs/chrono/0.4/chrono/offset/struct.FixedOffset.html) specifies
an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30.
This often results from the parsed textual date and time.
Since it stores the most information and does not depend on the system environment,
you would want to normalize other `TimeZone`s into this type.
`DateTime`s with different `TimeZone` types are distinct and do not mix,
but can be converted to each other using
the [`DateTime::with_timezone`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.with_timezone) method.
You can get the current date and time in the UTC time zone
([`Utc::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html#method.now))
or in the local time zone
([`Local::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html#method.now)).
```rust
use chrono::prelude::*;
let utc: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
let local: DateTime<Local> = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00`
```
Alternatively, you can create your own date and time.
This is a bit verbose due to Rust's lack of function and method overloading,
but in turn we get a rich combination of initialization methods.
```rust
use chrono::prelude::*;
use chrono::offset::LocalResult;
let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z`
// July 8 is 188th day of the year 2014 (`o` for "ordinal")
assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11));
// July 8 is Tuesday in ISO week 28 of the year 2014.
assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11));
let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z`
assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000));
assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000));
// dynamic verification
assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33),
LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33)));
assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None);
assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None);
// other time zone objects can be used to construct a local datetime.
// obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical.
let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12);
let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12);
assert_eq!(dt, fixed_dt);
```
Various properties are available to the date and time, and can be altered individually.
Most of them are defined in the traits [`Datelike`](https://docs.rs/chrono/0.4/chrono/trait.Datelike.html) and
[`Timelike`](https://docs.rs/chrono/0.4/chrono/trait.Timelike.html) which you should `use` before.
Addition and subtraction is also supported.
The following illustrates most supported operations to the date and time:
```rust
use chrono::prelude::*;
use chrono::Duration;
// assume this returned `2014-11-28T21:45:59.324310806+09:00`:
let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806);
// property accessors
assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28));
assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls
assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59));
assert_eq!(dt.weekday(), Weekday::Fri);
assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7
assert_eq!(dt.ordinal(), 332); // the day of year
assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1
// time zone accessor and manipulation
assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600);
assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600));
assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806));
// a sample of property manipulations (validates dynamically)
assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday
assert_eq!(dt.with_day(32), None);
assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE
// arithmetic operations
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2));
assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000),
Utc.ymd(2001, 9, 9).and_hms(1, 46, 40));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000),
Utc.ymd(1938, 4, 24).and_hms(22, 13, 20));
```
### Formatting and Parsing
Formatting is done via the [`format`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.format) method,
which format is equivalent to the familiar `strftime` format.
See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers)
documentation for full syntax and list of specifiers.
The default `to_string` method and `{:?}` specifier also give a reasonable representation.
Chrono also provides [`to_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc2822) and
[`to_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc3339) methods
for well-known formats.
Chrono now also provides date formatting in almost any language without the
help of an additional C library. This functionality is under the feature
`unstable-locales`:
```text
chrono { version = "0.4", features = ["unstable-locales"]
```
The `unstable-locales` feature requires and implies at least the `alloc` feature.
```rust
use chrono::prelude::*;
let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9);
assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09");
assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014");
assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09");
assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string());
assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC");
assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000");
assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00");
assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z");
// Note that milli/nanoseconds are only printed if they are non-zero
let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1);
assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z");
```
Parsing can be done with three methods:
1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait
(and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method
on a string) can be used for parsing `DateTime<FixedOffset>`, `DateTime<Utc>` and
`DateTime<Local>` values. This parses what the `{:?}`
([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html))
format specifier prints, and requires the offset to be present.
2. [`DateTime::parse_from_str`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_str) parses
a date and time with offsets and returns `DateTime<FixedOffset>`.
This should be used when the offset is a part of input and the caller cannot guess that.
It *cannot* be used when the offset can be missing.
[`DateTime::parse_from_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc2822)
and
[`DateTime::parse_from_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc3339)
are similar but for well-known formats.
3. [`Offset::datetime_from_str`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.datetime_from_str) is
similar but returns `DateTime` of given offset.
When the explicit offset is missing from the input, it simply uses given offset.
It issues an error when the input contains an explicit offset different
from the current offset.
More detailed control over the parsing process is available via
[`format`](https://docs.rs/chrono/0.4/chrono/format/index.html) module.
```rust
use chrono::prelude::*;
let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9);
let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600));
// method 1
assert_eq!("2014-11-28T12:00:09Z".parse::<DateTime<Utc>>(), Ok(dt.clone()));
assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<Utc>>(), Ok(dt.clone()));
assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<FixedOffset>>(), Ok(fixed_dt.clone()));
// method 2
assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"),
Ok(fixed_dt.clone()));
assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"),
Ok(fixed_dt.clone()));
assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
// method 3
assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone()));
assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone()));
// oops, the year is missing!
assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err());
// oops, the format string does not include the year at all!
assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
// oops, the weekday is incorrect!
assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());
```
Again : See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers)
documentation for full syntax and list of specifiers.
### Conversion from and to EPOCH timestamps
Use [`Utc.timestamp(seconds, nanoseconds)`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.timestamp)
to construct a [`DateTime<Utc>`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) from a UNIX timestamp
(seconds, nanoseconds that passed since January 1st 1970).
Use [`DateTime.timestamp`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp) to get the timestamp (in seconds)
from a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html). Additionally, you can use
[`DateTime.timestamp_subsec_nanos`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp_subsec_nanos)
to get the number of additional number of nanoseconds.
```rust
// We need the trait in scope to use Utc::timestamp().
use chrono::{DateTime, TimeZone, Utc};
// Construct a datetime from epoch:
let dt = Utc.timestamp(1_500_000_000, 0);
assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
// Get epoch value from a datetime:
let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
assert_eq!(dt.timestamp(), 1_500_000_000);
```
### Individual date
Chrono also provides an individual date type ([**`Date`**](https://docs.rs/chrono/0.4/chrono/struct.Date.html)).
It also has time zones attached, and have to be constructed via time zones.
Most operations available to `DateTime` are also available to `Date` whenever appropriate.
```rust
use chrono::prelude::*;
use chrono::offset::LocalResult;
assert_eq!(Utc::today(), Utc::now().date());
assert_eq!(Local::today(), Local::now().date());
assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri);
assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None);
assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(),
"070809");
```
There is no timezone-aware `Time` due to the lack of usefulness and also the complexity.
`DateTime` has [`date`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.date) method
which returns a `Date` which represents its date component.
There is also a [`time`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.time) method,
which simply returns a naive local time described below.
### Naive date and time
Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime`
as [**`NaiveDate`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html),
[**`NaiveTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html) and
[**`NaiveDateTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDateTime.html) respectively.
They have almost equivalent interfaces as their timezone-aware twins,
but are not associated to time zones obviously and can be quite low-level.
They are mostly useful for building blocks for higher-level types.
Timezone-aware `DateTime` and `Date` types have two methods returning naive versions:
[`naive_local`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_local) returns
a view to the naive local time,
and [`naive_utc`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_utc) returns
a view to the naive UTC time.
## Limitations
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(2014, 1, 30).with_month(2)` returns `None`.
Non ISO week handling is not yet supported.
For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext)
crate ([sources](https://github.com/bcourtine/chrono-ext/)).
Advanced time zone handling is not yet supported.
For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead.

View File

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

View File

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

View File

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

View File

@@ -2,73 +2,88 @@
// See README.md and LICENSE.txt for details.
//! ISO 8601 calendar date with time zone.
#![allow(deprecated)]
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg(feature = "alloc")]
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::ops::{Add, Sub};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::{fmt, hash};
use oldtime::Duration as OldDuration;
#[cfg(feature = "unstable-locales")]
use format::Locale;
#[cfg(any(feature = "alloc", feature = "std", test))]
use format::{DelayedFormat, Item, StrftimeItems};
use naive::{self, IsoWeek, NaiveDate, NaiveTime};
use offset::{TimeZone, Utc};
use DateTime;
use {Datelike, Weekday};
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};
#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
use crate::format::Locale;
#[cfg(feature = "alloc")]
use crate::format::{DelayedFormat, Item, StrftimeItems};
use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
use crate::offset::{TimeZone, Utc};
use crate::{DateTime, Datelike, TimeDelta, Weekday};
/// ISO 8601 calendar date with time zone.
///
/// This type should be considered ambiguous at best,
/// due to the inherent lack of precision required for the time zone resolution.
/// For serialization and deserialization uses, it is best to use `NaiveDate` instead.
/// You almost certainly want to be using a [`NaiveDate`] instead of this type.
///
/// This type primarily exists to aid in the construction of DateTimes that
/// have a timezone by way of the [`TimeZone`] datelike constructors (e.g.
/// [`TimeZone::ymd`]).
///
/// This type should be considered ambiguous at best, due to the inherent lack
/// of precision required for the time zone resolution.
///
/// There are some guarantees on the usage of `Date<Tz>`:
///
/// - If properly constructed via `TimeZone::ymd` and others without an error,
/// - If properly constructed via [`TimeZone::ymd`] and others without an error,
/// the corresponding local date should exist for at least a moment.
/// (It may still have a gap from the offset changes.)
///
/// - The `TimeZone` is free to assign *any* `Offset` to the local date,
/// as long as that offset did occur in given day.
/// - The `TimeZone` is free to assign *any* [`Offset`](crate::offset::Offset) to the
/// local date, as long as that offset did occur in given day.
///
/// For example, if `2015-03-08T01:59-08:00` is followed by `2015-03-08T03:00-07:00`,
/// it may produce either `2015-03-08-08:00` or `2015-03-08-07:00`
/// but *not* `2015-03-08+00:00` and others.
///
/// - Once constructed as a full `DateTime`,
/// `DateTime::date` and other associated methods should return those for the original `Date`.
/// For example, if `dt = tz.ymd(y,m,d).hms(h,n,s)` were valid, `dt.date() == tz.ymd(y,m,d)`.
/// - Once constructed as a full `DateTime`, [`DateTime::date`] and other associated
/// methods should return those for the original `Date`. For example, if `dt =
/// tz.ymd_opt(y,m,d).unwrap().hms(h,n,s)` were valid, `dt.date() == tz.ymd_opt(y,m,d).unwrap()`.
///
/// - The date is timezone-agnostic up to one day (i.e. practically always),
/// so the local date and UTC date should be equal for most cases
/// even though the raw calculation between `NaiveDate` and `Duration` may not.
/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not.
#[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime<Tz>` instead")]
#[derive(Clone)]
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
pub struct Date<Tz: TimeZone> {
date: NaiveDate,
offset: Tz::Offset,
}
/// The minimum possible `Date`.
pub const MIN_DATE: Date<Utc> = Date { date: naive::MIN_DATE, offset: Utc };
#[allow(deprecated)]
#[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")]
pub const MIN_DATE: Date<Utc> = Date::<Utc>::MIN_UTC;
/// The maximum possible `Date`.
pub const MAX_DATE: Date<Utc> = Date { date: naive::MAX_DATE, offset: Utc };
#[allow(deprecated)]
#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")]
pub const MAX_DATE: Date<Utc> = Date::<Utc>::MAX_UTC;
impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `Date` with given *UTC* date and offset.
/// The local date should be constructed via the `TimeZone` trait.
//
// note: this constructor is purposely not named to `new` to discourage the direct usage.
#[inline]
#[must_use]
pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date<Tz> {
Date { date: date, offset: offset }
Date { date, offset }
}
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
/// The offset in the current date is preserved.
///
/// Panics on invalid datetime.
/// Returns `None` on invalid datetime.
#[inline]
#[must_use]
pub fn and_time(&self, time: NaiveTime) -> Option<DateTime<Tz>> {
let localdt = self.naive_local().and_time(time);
self.timezone().from_local_datetime(&localdt).single()
@@ -78,7 +93,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// The offset in the current date is preserved.
///
/// Panics on invalid hour, minute and/or second.
#[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")]
#[inline]
#[must_use]
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime<Tz> {
self.and_hms_opt(hour, min, sec).expect("invalid time")
}
@@ -88,6 +105,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute and/or second.
#[inline]
#[must_use]
pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<DateTime<Tz>> {
NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time))
}
@@ -97,7 +115,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// The offset in the current date is preserved.
///
/// Panics on invalid hour, minute, second and/or millisecond.
#[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")]
#[inline]
#[must_use]
pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime<Tz> {
self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time")
}
@@ -108,6 +128,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
#[inline]
#[must_use]
pub fn and_hms_milli_opt(
&self,
hour: u32,
@@ -123,7 +144,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// The offset in the current date is preserved.
///
/// Panics on invalid hour, minute, second and/or microsecond.
#[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")]
#[inline]
#[must_use]
pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime<Tz> {
self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time")
}
@@ -134,6 +157,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
#[inline]
#[must_use]
pub fn and_hms_micro_opt(
&self,
hour: u32,
@@ -149,7 +173,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// The offset in the current date is preserved.
///
/// Panics on invalid hour, minute, second and/or nanosecond.
#[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")]
#[inline]
#[must_use]
pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime<Tz> {
self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time")
}
@@ -160,6 +186,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
#[inline]
#[must_use]
pub fn and_hms_nano_opt(
&self,
hour: u32,
@@ -173,7 +200,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `Date` for the next date.
///
/// Panics when `self` is the last representable date.
#[deprecated(since = "0.4.23", note = "Use succ_opt() instead")]
#[inline]
#[must_use]
pub fn succ(&self) -> Date<Tz> {
self.succ_opt().expect("out of bound")
}
@@ -182,6 +211,7 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` when `self` is the last representable date.
#[inline]
#[must_use]
pub fn succ_opt(&self) -> Option<Date<Tz>> {
self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
@@ -189,7 +219,9 @@ impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `Date` for the prior date.
///
/// Panics when `self` is the first representable date.
#[deprecated(since = "0.4.23", note = "Use pred_opt() instead")]
#[inline]
#[must_use]
pub fn pred(&self) -> Date<Tz> {
self.pred_opt().expect("out of bound")
}
@@ -198,18 +230,21 @@ impl<Tz: TimeZone> Date<Tz> {
///
/// Returns `None` when `self` is the first representable date.
#[inline]
#[must_use]
pub fn pred_opt(&self) -> Option<Date<Tz>> {
self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
/// Retrieves an associated offset from UTC.
#[inline]
#[must_use]
pub fn offset(&self) -> &Tz::Offset {
&self.offset
}
/// Retrieves an associated time zone.
#[inline]
#[must_use]
pub fn timezone(&self) -> Tz {
TimeZone::from_offset(&self.offset)
}
@@ -217,40 +252,45 @@ impl<Tz: TimeZone> Date<Tz> {
/// Changes the associated time zone.
/// This does not change the actual `Date` (but will change the string representation).
#[inline]
#[must_use]
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> Date<Tz2> {
tz.from_utc_date(&self.date)
}
/// Adds given `Duration` to the current date.
/// Adds given `TimeDelta` to the current date.
///
/// Returns `None` when it will result in overflow.
#[inline]
pub fn checked_add_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
let date = try_opt!(self.date.checked_add_signed(rhs));
Some(Date { date: date, offset: self.offset })
#[must_use]
pub fn checked_add_signed(self, rhs: TimeDelta) -> Option<Date<Tz>> {
let date = self.date.checked_add_signed(rhs)?;
Some(Date { date, offset: self.offset })
}
/// Subtracts given `Duration` from the current date.
/// Subtracts given `TimeDelta` from the current date.
///
/// Returns `None` when it will result in overflow.
#[inline]
pub fn checked_sub_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
let date = try_opt!(self.date.checked_sub_signed(rhs));
Some(Date { date: date, offset: self.offset })
#[must_use]
pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option<Date<Tz>> {
let date = self.date.checked_sub_signed(rhs)?;
Some(Date { date, offset: self.offset })
}
/// Subtracts another `Date` from the current date.
/// Returns a `Duration` of integral numbers.
/// Returns a `TimeDelta` of integral numbers.
///
/// This does not overflow or underflow at all,
/// as all possible output fits in the range of `Duration`.
/// as all possible output fits in the range of `TimeDelta`.
#[inline]
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> OldDuration {
#[must_use]
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> TimeDelta {
self.date.signed_duration_since(rhs.date)
}
/// Returns a view to the naive UTC date.
#[inline]
#[must_use]
pub fn naive_utc(&self) -> NaiveDate {
self.date
}
@@ -261,9 +301,21 @@ impl<Tz: TimeZone> Date<Tz> {
/// because the offset is restricted to never exceed one day,
/// but provided for the consistency.
#[inline]
#[must_use]
pub fn naive_local(&self) -> NaiveDate {
self.date
}
/// Returns the number of whole years from the given `base` until `self`.
#[must_use]
pub fn years_since(&self, base: Self) -> Option<u32> {
self.date.years_since(base.date)
}
/// The minimum possible `Date`.
pub const MIN_UTC: Date<Utc> = Date { date: NaiveDate::MIN, offset: Utc };
/// The maximum possible `Date`.
pub const MAX_UTC: Date<Utc> = Date { date: NaiveDate::MAX, offset: Utc };
}
/// Maps the local date to other date with given conversion function.
@@ -279,8 +331,9 @@ where
Tz::Offset: fmt::Display,
{
/// Formats the date with the specified formatting items.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where
I: Iterator<Item = B> + Clone,
@@ -290,17 +343,19 @@ where
}
/// Formats the date with the specified format string.
/// See the [`format::strftime` module](./format/strftime/index.html)
/// See the [`crate::format::strftime`] module
/// on the supported escape sequences.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
self.format_with_items(StrftimeItems::new(fmt))
}
/// Formats the date with the specified formatting items and locale.
#[cfg(feature = "unstable-locales")]
#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
#[inline]
#[must_use]
pub fn format_localized_with_items<'a, I, B>(
&self,
items: I,
@@ -320,10 +375,11 @@ where
}
/// Formats the date with the specified format string and locale.
/// See the [`format::strftime` module](./format/strftime/index.html)
/// See the [`crate::format::strftime`] module
/// on the supported escape sequences.
#[cfg(feature = "unstable-locales")]
#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
#[inline]
#[must_use]
pub fn format_localized<'a>(
&self,
fmt: &'a str,
@@ -421,7 +477,7 @@ impl<Tz: TimeZone> Eq for Date<Tz> {}
impl<Tz: TimeZone> PartialOrd for Date<Tz> {
fn partial_cmp(&self, other: &Date<Tz>) -> Option<Ordering> {
self.date.partial_cmp(&other.date)
Some(self.cmp(other))
}
}
@@ -437,36 +493,51 @@ impl<Tz: TimeZone> hash::Hash for Date<Tz> {
}
}
impl<Tz: TimeZone> Add<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> Add<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
fn add(self, rhs: OldDuration) -> Date<Tz> {
self.checked_add_signed(rhs).expect("`Date + Duration` overflowed")
fn add(self, rhs: TimeDelta) -> Date<Tz> {
self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed")
}
}
impl<Tz: TimeZone> Sub<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> AddAssign<TimeDelta> for Date<Tz> {
#[inline]
fn add_assign(&mut self, rhs: TimeDelta) {
self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed");
}
}
impl<Tz: TimeZone> Sub<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;
#[inline]
fn sub(self, rhs: OldDuration) -> Date<Tz> {
self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed")
fn sub(self, rhs: TimeDelta) -> Date<Tz> {
self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed")
}
}
impl<Tz: TimeZone> SubAssign<TimeDelta> for Date<Tz> {
#[inline]
fn sub_assign(&mut self, rhs: TimeDelta) {
self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed");
}
}
impl<Tz: TimeZone> Sub<Date<Tz>> for Date<Tz> {
type Output = OldDuration;
type Output = TimeDelta;
#[inline]
fn sub(self, rhs: Date<Tz>) -> OldDuration {
fn sub(self, rhs: Date<Tz>) -> TimeDelta {
self.signed_duration_since(rhs)
}
}
impl<Tz: TimeZone> fmt::Debug for Date<Tz> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}{:?}", self.naive_local(), self.offset)
self.naive_local().fmt(f)?;
self.offset.fmt(f)
}
}
@@ -475,6 +546,118 @@ where
Tz::Offset: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.naive_local(), self.offset)
self.naive_local().fmt(f)?;
self.offset.fmt(f)
}
}
// Note that implementation of Arbitrary cannot be automatically derived for Date<Tz>, due to
// the nontrivial bound <Tz as TimeZone>::Offset: Arbitrary.
#[cfg(all(feature = "arbitrary", feature = "std"))]
impl<'a, Tz> arbitrary::Arbitrary<'a> for Date<Tz>
where
Tz: TimeZone,
<Tz as TimeZone>::Offset: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Date<Tz>> {
let date = NaiveDate::arbitrary(u)?;
let offset = <Tz as TimeZone>::Offset::arbitrary(u)?;
Ok(Date::from_utc(date, offset))
}
}
#[cfg(test)]
mod tests {
use super::Date;
use crate::{FixedOffset, NaiveDate, TimeDelta, Utc};
#[cfg(feature = "clock")]
use crate::offset::{Local, TimeZone};
#[test]
#[cfg(feature = "clock")]
fn test_years_elapsed() {
const WEEKS_PER_YEAR: f32 = 52.1775;
// This is always at least one year because 1 year = 52.1775 weeks.
let one_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
// A bit more than 2 years.
let two_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64);
assert_eq!(Utc::today().years_since(one_year_ago), Some(1));
assert_eq!(Utc::today().years_since(two_year_ago), Some(2));
// If the given DateTime is later than now, the function will always return 0.
let future = Utc::today() + TimeDelta::weeks(12);
assert_eq!(Utc::today().years_since(future), None);
}
#[test]
fn test_date_add_assign() {
let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
let date = Date::<Utc>::from_utc(naivedate, Utc);
let mut date_add = date;
date_add += TimeDelta::days(5);
assert_eq!(date_add, date + TimeDelta::days(5));
let timezone = FixedOffset::east_opt(60 * 60).unwrap();
let date = date.with_timezone(&timezone);
let date_add = date_add.with_timezone(&timezone);
assert_eq!(date_add, date + TimeDelta::days(5));
let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap();
let date = date.with_timezone(&timezone);
let date_add = date_add.with_timezone(&timezone);
assert_eq!(date_add, date + TimeDelta::days(5));
}
#[test]
#[cfg(feature = "clock")]
fn test_date_add_assign_local() {
let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
let date = Local.from_utc_date(&naivedate);
let mut date_add = date;
date_add += TimeDelta::days(5);
assert_eq!(date_add, date + TimeDelta::days(5));
}
#[test]
fn test_date_sub_assign() {
let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
let date = Date::<Utc>::from_utc(naivedate, Utc);
let mut date_sub = date;
date_sub -= TimeDelta::days(5);
assert_eq!(date_sub, date - TimeDelta::days(5));
let timezone = FixedOffset::east_opt(60 * 60).unwrap();
let date = date.with_timezone(&timezone);
let date_sub = date_sub.with_timezone(&timezone);
assert_eq!(date_sub, date - TimeDelta::days(5));
let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap();
let date = date.with_timezone(&timezone);
let date_sub = date_sub.with_timezone(&timezone);
assert_eq!(date_sub, date - TimeDelta::days(5));
}
#[test]
#[cfg(feature = "clock")]
fn test_date_sub_assign_local() {
let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
let date = Local.from_utc_date(&naivedate);
let mut date_sub = date;
date_sub -= TimeDelta::days(5);
assert_eq!(date_sub, date - TimeDelta::days(5));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +0,0 @@
// This is a part of Chrono.
// Portions Copyright 2013-2014 The Rust Project Developers.
// See README.md and LICENSE.txt for details.
//! Integer division utilities. (Shamelessly copied from [num](https://github.com/rust-lang/num/))
// Algorithm from [Daan Leijen. _Division and Modulus for Computer Scientists_,
// December 2001](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf)
pub use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor};
#[cfg(test)]
mod tests {
use super::{div_mod_floor, mod_floor};
#[test]
fn test_mod_floor() {
assert_eq!(mod_floor(8, 3), 2);
assert_eq!(mod_floor(8, -3), -1);
assert_eq!(mod_floor(-8, 3), 1);
assert_eq!(mod_floor(-8, -3), -2);
assert_eq!(mod_floor(1, 2), 1);
assert_eq!(mod_floor(1, -2), -1);
assert_eq!(mod_floor(-1, 2), 1);
assert_eq!(mod_floor(-1, -2), -1);
}
#[test]
fn test_div_mod_floor() {
assert_eq!(div_mod_floor(8, 3), (2, 2));
assert_eq!(div_mod_floor(8, -3), (-3, -1));
assert_eq!(div_mod_floor(-8, 3), (-3, 1));
assert_eq!(div_mod_floor(-8, -3), (2, -2));
assert_eq!(div_mod_floor(1, 2), (0, 1));
assert_eq!(div_mod_floor(1, -2), (-1, -1));
assert_eq!(div_mod_floor(-1, 2), (-1, 1));
assert_eq!(div_mod_floor(-1, -2), (0, -1));
}
}

View File

@@ -0,0 +1,945 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
//! Date and time formatting routines.
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::string::{String, ToString};
#[cfg(feature = "alloc")]
use core::borrow::Borrow;
#[cfg(feature = "alloc")]
use core::fmt::Display;
use core::fmt::{self, Write};
#[cfg(feature = "alloc")]
use crate::offset::Offset;
#[cfg(any(feature = "alloc", feature = "serde"))]
use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike};
#[cfg(feature = "alloc")]
use crate::{NaiveDate, NaiveTime, Weekday};
#[cfg(feature = "alloc")]
use super::locales;
#[cfg(any(feature = "alloc", feature = "serde"))]
use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
#[cfg(feature = "alloc")]
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric};
#[cfg(feature = "alloc")]
use locales::*;
/// A *temporary* object which can be used as an argument to `format!` or others.
/// This is normally constructed via `format` methods of each date and time type.
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub struct DelayedFormat<I> {
/// The date view, if any.
date: Option<NaiveDate>,
/// The time view, if any.
time: Option<NaiveTime>,
/// The name and local-to-UTC difference for the offset (timezone), if any.
off: Option<(String, FixedOffset)>,
/// An iterator returning formatting items.
items: I,
/// Locale used for text.
/// ZST if the `unstable-locales` feature is not enabled.
locale: Locale,
}
#[cfg(feature = "alloc")]
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
/// Makes a new `DelayedFormat` value out of local date and time.
#[must_use]
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
DelayedFormat { date, time, off: None, items, locale: default_locale() }
}
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
#[must_use]
pub fn new_with_offset<Off>(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
offset: &Off,
items: I,
) -> DelayedFormat<I>
where
Off: Offset + Display,
{
let name_and_diff = (offset.to_string(), offset.fix());
DelayedFormat { date, time, off: Some(name_and_diff), items, locale: default_locale() }
}
/// Makes a new `DelayedFormat` value out of local date and time and locale.
#[cfg(feature = "unstable-locales")]
#[must_use]
pub fn new_with_locale(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
items: I,
locale: Locale,
) -> DelayedFormat<I> {
DelayedFormat { date, time, off: None, items, locale }
}
/// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
#[cfg(feature = "unstable-locales")]
#[must_use]
pub fn new_with_offset_and_locale<Off>(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
offset: &Off,
items: I,
locale: Locale,
) -> DelayedFormat<I>
where
Off: Offset + Display,
{
let name_and_diff = (offset.to_string(), offset.fix());
DelayedFormat { date, time, off: Some(name_and_diff), items, locale }
}
/// Formats `DelayedFormat` into a `core::fmt::Write` instance.
/// # Errors
/// This function returns a `core::fmt::Error` if formatting into the `core::fmt::Write` instance fails.
///
/// # Example
/// ### Writing to a String
/// ```
/// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap();
/// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f");
/// let mut buffer = String::new();
/// let _ = df.write_to(&mut buffer);
/// ```
pub fn write_to(&self, w: &mut impl Write) -> fmt::Result {
for item in self.items.clone() {
match *item.borrow() {
Item::Literal(s) | Item::Space(s) => w.write_str(s),
#[cfg(feature = "alloc")]
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s),
Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad),
Item::Fixed(ref spec) => self.format_fixed(w, spec),
Item::Error => Err(fmt::Error),
}?;
}
Ok(())
}
#[cfg(feature = "alloc")]
fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result {
use self::Numeric::*;
fn write_one(w: &mut impl Write, v: u8) -> fmt::Result {
w.write_char((b'0' + v) as char)
}
fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result {
let ones = b'0' + v % 10;
match (v / 10, pad) {
(0, Pad::None) => {}
(0, Pad::Space) => w.write_char(' ')?,
(tens, _) => w.write_char((b'0' + tens) as char)?,
}
w.write_char(ones as char)
}
#[inline]
fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result {
if (1000..=9999).contains(&year) {
// fast path
write_hundreds(w, (year / 100) as u8)?;
write_hundreds(w, (year % 100) as u8)
} else {
write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year))
}
}
fn write_n(
w: &mut impl Write,
n: usize,
v: i64,
pad: Pad,
always_sign: bool,
) -> fmt::Result {
if always_sign {
match pad {
Pad::None => write!(w, "{:+}", v),
Pad::Zero => write!(w, "{:+01$}", v, n + 1),
Pad::Space => write!(w, "{:+1$}", v, n + 1),
}
} else {
match pad {
Pad::None => write!(w, "{}", v),
Pad::Zero => write!(w, "{:01$}", v, n),
Pad::Space => write!(w, "{:1$}", v, n),
}
}
}
match (spec, self.date, self.time) {
(Year, Some(d), _) => write_year(w, d.year(), pad),
(YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad),
(YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad),
(IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad),
(IsoYearDiv100, Some(d), _) => {
write_two(w, d.iso_week().year().div_euclid(100) as u8, pad)
}
(IsoYearMod100, Some(d), _) => {
write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad)
}
(Quarter, Some(d), _) => write_one(w, d.quarter() as u8),
(Month, Some(d), _) => write_two(w, d.month() as u8, pad),
(Day, Some(d), _) => write_two(w, d.day() as u8, pad),
(WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad),
(WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad),
(IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad),
(NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8),
(WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8),
(Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false),
(Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad),
(Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad),
(Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad),
(Second, _, Some(t)) => {
write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad)
}
(Nanosecond, _, Some(t)) => {
write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false)
}
(Timestamp, Some(d), Some(t)) => {
let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc()));
let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0);
write_n(w, 9, timestamp, pad, false)
}
(Internal(_), _, _) => Ok(()), // for future expansion
_ => Err(fmt::Error), // insufficient arguments for given format
}
}
#[cfg(feature = "alloc")]
fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result {
use Fixed::*;
use InternalInternal::*;
match (spec, self.date, self.time, self.off.as_ref()) {
(ShortMonthName, Some(d), _, _) => {
w.write_str(short_months(self.locale)[d.month0() as usize])
}
(LongMonthName, Some(d), _, _) => {
w.write_str(long_months(self.locale)[d.month0() as usize])
}
(ShortWeekdayName, Some(d), _, _) => w.write_str(
short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize],
),
(LongWeekdayName, Some(d), _, _) => {
w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize])
}
(LowerAmPm, _, Some(t), _) => {
let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
for c in ampm.chars().flat_map(|c| c.to_lowercase()) {
w.write_char(c)?
}
Ok(())
}
(UpperAmPm, _, Some(t), _) => {
let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
w.write_str(ampm)
}
(Nanosecond, _, Some(t), _) => {
let nano = t.nanosecond() % 1_000_000_000;
if nano == 0 {
Ok(())
} else {
w.write_str(decimal_point(self.locale))?;
if nano % 1_000_000 == 0 {
write!(w, "{:03}", nano / 1_000_000)
} else if nano % 1_000 == 0 {
write!(w, "{:06}", nano / 1_000)
} else {
write!(w, "{:09}", nano)
}
}
}
(Nanosecond3, _, Some(t), _) => {
w.write_str(decimal_point(self.locale))?;
write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000)
}
(Nanosecond6, _, Some(t), _) => {
w.write_str(decimal_point(self.locale))?;
write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
}
(Nanosecond9, _, Some(t), _) => {
w.write_str(decimal_point(self.locale))?;
write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
}
(Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => {
write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000)
}
(Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => {
write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
}
(Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => {
write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
}
(TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{}", tz_name),
(TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => {
let offset_format = OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::Maybe,
allow_zulu: *spec == TimezoneOffsetZ,
padding: Pad::Zero,
};
offset_format.format(w, *off)
}
(TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => {
let offset_format = OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::Colon,
allow_zulu: *spec == TimezoneOffsetColonZ,
padding: Pad::Zero,
};
offset_format.format(w, *off)
}
(TimezoneOffsetDoubleColon, _, _, Some((_, off))) => {
let offset_format = OffsetFormat {
precision: OffsetPrecision::Seconds,
colons: Colons::Colon,
allow_zulu: false,
padding: Pad::Zero,
};
offset_format.format(w, *off)
}
(TimezoneOffsetTripleColon, _, _, Some((_, off))) => {
let offset_format = OffsetFormat {
precision: OffsetPrecision::Hours,
colons: Colons::None,
allow_zulu: false,
padding: Pad::Zero,
};
offset_format.format(w, *off)
}
(RFC2822, Some(d), Some(t), Some((_, off))) => {
write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off)
}
(RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339(
w,
crate::NaiveDateTime::new(d, t),
*off,
SecondsFormat::AutoSi,
false,
),
_ => Err(fmt::Error), // insufficient arguments for given format
}
}
}
#[cfg(feature = "alloc")]
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut result = String::new();
self.write_to(&mut result)?;
f.pad(&result)
}
}
/// Tries to format given arguments with given formatting items.
/// Internally used by `DelayedFormat`.
#[cfg(feature = "alloc")]
#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")]
pub fn format<'a, I, B>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
items: I,
) -> fmt::Result
where
I: Iterator<Item = B> + Clone,
B: Borrow<Item<'a>>,
{
DelayedFormat {
date: date.copied(),
time: time.copied(),
off: off.cloned(),
items,
locale: default_locale(),
}
.fmt(w)
}
/// Formats single formatting item
#[cfg(feature = "alloc")]
#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")]
pub fn format_item(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
item: &Item<'_>,
) -> fmt::Result {
DelayedFormat {
date: date.copied(),
time: time.copied(),
off: off.cloned(),
items: [item].into_iter(),
locale: default_locale(),
}
.fmt(w)
}
#[cfg(any(feature = "alloc", feature = "serde"))]
impl OffsetFormat {
/// Writes an offset from UTC with the format defined by `self`.
fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result {
let off = off.local_minus_utc();
if self.allow_zulu && off == 0 {
w.write_char('Z')?;
return Ok(());
}
let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
let hours;
let mut mins = 0;
let mut secs = 0;
let precision = match self.precision {
OffsetPrecision::Hours => {
// Minutes and seconds are simply truncated
hours = (off / 3600) as u8;
OffsetPrecision::Hours
}
OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => {
// Round seconds to the nearest minute.
let minutes = (off + 30) / 60;
mins = (minutes % 60) as u8;
hours = (minutes / 60) as u8;
if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 {
OffsetPrecision::Hours
} else {
OffsetPrecision::Minutes
}
}
OffsetPrecision::Seconds
| OffsetPrecision::OptionalSeconds
| OffsetPrecision::OptionalMinutesAndSeconds => {
let minutes = off / 60;
secs = (off % 60) as u8;
mins = (minutes % 60) as u8;
hours = (minutes / 60) as u8;
if self.precision != OffsetPrecision::Seconds && secs == 0 {
if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 {
OffsetPrecision::Hours
} else {
OffsetPrecision::Minutes
}
} else {
OffsetPrecision::Seconds
}
}
};
let colons = self.colons == Colons::Colon;
if hours < 10 {
if self.padding == Pad::Space {
w.write_char(' ')?;
}
w.write_char(sign)?;
if self.padding == Pad::Zero {
w.write_char('0')?;
}
w.write_char((b'0' + hours) as char)?;
} else {
w.write_char(sign)?;
write_hundreds(w, hours)?;
}
if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision {
if colons {
w.write_char(':')?;
}
write_hundreds(w, mins)?;
}
if let OffsetPrecision::Seconds = precision {
if colons {
w.write_char(':')?;
}
write_hundreds(w, secs)?;
}
Ok(())
}
}
/// Specific formatting options for seconds. This may be extended in the
/// future, so exhaustive matching in external code is not recommended.
///
/// See the `TimeZone::to_rfc3339_opts` function for usage.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[allow(clippy::manual_non_exhaustive)]
pub enum SecondsFormat {
/// Format whole seconds only, with no decimal point nor subseconds.
Secs,
/// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3].
Millis,
/// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6].
Micros,
/// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9].
Nanos,
/// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available
/// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond].
AutoSi,
// Do not match against this.
#[doc(hidden)]
__NonExhaustive,
}
/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
#[inline]
#[cfg(any(feature = "alloc", feature = "serde"))]
pub(crate) fn write_rfc3339(
w: &mut impl Write,
dt: NaiveDateTime,
off: FixedOffset,
secform: SecondsFormat,
use_z: bool,
) -> fmt::Result {
let year = dt.date().year();
if (0..=9999).contains(&year) {
write_hundreds(w, (year / 100) as u8)?;
write_hundreds(w, (year % 100) as u8)?;
} else {
// ISO 8601 requires the explicit sign for out-of-range years
write!(w, "{:+05}", year)?;
}
w.write_char('-')?;
write_hundreds(w, dt.date().month() as u8)?;
w.write_char('-')?;
write_hundreds(w, dt.date().day() as u8)?;
w.write_char('T')?;
let (hour, min, mut sec) = dt.time().hms();
let mut nano = dt.nanosecond();
if nano >= 1_000_000_000 {
sec += 1;
nano -= 1_000_000_000;
}
write_hundreds(w, hour as u8)?;
w.write_char(':')?;
write_hundreds(w, min as u8)?;
w.write_char(':')?;
let sec = sec;
write_hundreds(w, sec as u8)?;
match secform {
SecondsFormat::Secs => {}
SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?,
SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?,
SecondsFormat::Nanos => write!(w, ".{:09}", nano)?,
SecondsFormat::AutoSi => {
if nano == 0 {
} else if nano % 1_000_000 == 0 {
write!(w, ".{:03}", nano / 1_000_000)?
} else if nano % 1_000 == 0 {
write!(w, ".{:06}", nano / 1_000)?
} else {
write!(w, ".{:09}", nano)?
}
}
SecondsFormat::__NonExhaustive => unreachable!(),
};
OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::Colon,
allow_zulu: use_z,
padding: Pad::Zero,
}
.format(w, off)
}
#[cfg(feature = "alloc")]
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
pub(crate) fn write_rfc2822(
w: &mut impl Write,
dt: NaiveDateTime,
off: FixedOffset,
) -> fmt::Result {
let year = dt.year();
// RFC2822 is only defined on years 0 through 9999
if !(0..=9999).contains(&year) {
return Err(fmt::Error);
}
let english = default_locale();
w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?;
w.write_str(", ")?;
let day = dt.day();
if day < 10 {
w.write_char((b'0' + day as u8) as char)?;
} else {
write_hundreds(w, day as u8)?;
}
w.write_char(' ')?;
w.write_str(short_months(english)[dt.month0() as usize])?;
w.write_char(' ')?;
write_hundreds(w, (year / 100) as u8)?;
write_hundreds(w, (year % 100) as u8)?;
w.write_char(' ')?;
let (hour, min, sec) = dt.time().hms();
write_hundreds(w, hour as u8)?;
w.write_char(':')?;
write_hundreds(w, min as u8)?;
w.write_char(':')?;
let sec = sec + dt.nanosecond() / 1_000_000_000;
write_hundreds(w, sec as u8)?;
w.write_char(' ')?;
OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::None,
allow_zulu: false,
padding: Pad::Zero,
}
.format(w, off)
}
/// Equivalent to `{:02}` formatting for n < 100.
pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
if n >= 100 {
return Err(fmt::Error);
}
let tens = b'0' + n / 10;
let ones = b'0' + n % 10;
w.write_char(tens as char)?;
w.write_char(ones as char)
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
use crate::FixedOffset;
#[cfg(feature = "alloc")]
use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc};
#[cfg(feature = "alloc")]
#[test]
fn test_delayed_write_to() {
let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap();
let df = dt.format("%Y-%m-%d %H:%M:%S%.9f");
let mut dt_str = String::new();
df.write_to(&mut dt_str).unwrap();
assert_eq!(dt_str, "2022-02-01 13:50:00.123456789");
}
#[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))]
#[test]
fn test_with_locale_delayed_write_to() {
use crate::DateTime;
use crate::format::locales::Locale;
let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap();
let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP);
let mut dt_str = String::new();
df.write_to(&mut dt_str).unwrap();
assert_eq!(dt_str, "火曜日, 2月 01, 2022");
}
#[test]
#[cfg(feature = "alloc")]
fn test_date_format() {
let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap();
assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12");
assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March");
assert_eq!(d.format("%q").to_string(), "1");
assert_eq!(d.format("%d,%e").to_string(), "04, 4");
assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09");
assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7");
assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year
assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12");
assert_eq!(d.format("%F").to_string(), "2012-03-04");
assert_eq!(d.format("%v").to_string(), " 4-Mar-2012");
assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
// non-four-digit years
assert_eq!(
NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(),
"+12345"
);
assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234");
assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123");
assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012");
assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001");
assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000");
assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001");
assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012");
assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123");
assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234");
assert_eq!(
NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(),
"-12345"
);
// corner cases
assert_eq!(
NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(),
"2008,08,52,53,01"
);
assert_eq!(
NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(),
"2009,09,01,00,53"
);
}
#[test]
#[cfg(feature = "alloc")]
fn test_time_format() {
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM");
assert_eq!(t.format("%M").to_string(), "05");
assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432");
assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432");
assert_eq!(t.format("%R").to_string(), "03:05");
assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07");
assert_eq!(t.format("%r").to_string(), "03:05:07 AM");
assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap();
assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100");
assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000");
let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap();
assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210");
assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000");
let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap();
assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,");
assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000");
// corner cases
assert_eq!(
NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(),
"01:57:09 PM"
);
assert_eq!(
NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(),
"23:59:60"
);
}
#[test]
#[cfg(feature = "alloc")]
fn test_datetime_format() {
let dt =
NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap();
assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010");
assert_eq!(dt.format("%s").to_string(), "1283929614");
assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
// a horror of leap second: coming near to you.
let dt = NaiveDate::from_ymd_opt(2012, 6, 30)
.unwrap()
.and_hms_milli_opt(23, 59, 59, 1_000)
.unwrap();
assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012");
assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional.
}
#[test]
#[cfg(feature = "alloc")]
fn test_datetime_format_alignment() {
let datetime = Utc
.with_ymd_and_hms(2007, 1, 2, 12, 34, 56)
.unwrap()
.with_nanosecond(123456789)
.unwrap();
// Item::Literal, odd number of padding bytes.
let percent = datetime.format("%%");
assert_eq!(" %", format!("{:>4}", percent));
assert_eq!("% ", format!("{:<4}", percent));
assert_eq!(" % ", format!("{:^4}", percent));
// Item::Numeric, custom non-ASCII padding character
let year = datetime.format("%Y");
assert_eq!("——2007", format!("{:—>6}", year));
assert_eq!("2007——", format!("{:—<6}", year));
assert_eq!("—2007—", format!("{:—^6}", year));
// Item::Fixed
let tz = datetime.format("%Z");
assert_eq!(" UTC", format!("{:>5}", tz));
assert_eq!("UTC ", format!("{:<5}", tz));
assert_eq!(" UTC ", format!("{:^5}", tz));
// [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric]
let ymd = datetime.format("%Y %B %d");
assert_eq!(" 2007 January 02", format!("{:>17}", ymd));
assert_eq!("2007 January 02 ", format!("{:<17}", ymd));
assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd));
// Truncated
let time = datetime.format("%T%.6f");
assert_eq!("12:34:56.1234", format!("{:.13}", time));
}
#[test]
fn test_offset_formatting() {
fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) {
fn check(
precision: OffsetPrecision,
colons: Colons,
padding: Pad,
allow_zulu: bool,
offsets: [FixedOffset; 7],
expected: [&str; 7],
) {
let offset_format = OffsetFormat { precision, colons, allow_zulu, padding };
for (offset, expected) in offsets.iter().zip(expected.iter()) {
let mut output = String::new();
offset_format.format(&mut output, *offset).unwrap();
assert_eq!(&output, expected);
}
}
// +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00
let offsets = [
FixedOffset::east_opt(13_500).unwrap(),
FixedOffset::east_opt(-12_600).unwrap(),
FixedOffset::east_opt(39_600).unwrap(),
FixedOffset::east_opt(-39_622).unwrap(),
FixedOffset::east_opt(9266).unwrap(),
FixedOffset::east_opt(-45270).unwrap(),
FixedOffset::east_opt(0).unwrap(),
];
check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]);
check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]);
check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]);
check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]);
check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]);
check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]);
check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]);
check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]);
check(precision, Colons::None, Pad::Space, false, offsets, expected[8]);
check(precision, Colons::None, Pad::Space, true, offsets, expected[9]);
check(precision, Colons::None, Pad::None, false, offsets, expected[10]);
check(precision, Colons::None, Pad::None, true, offsets, expected[11]);
// `Colons::Maybe` should format the same as `Colons::None`
check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]);
check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]);
check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]);
check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]);
check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]);
check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]);
}
check_all(
OffsetPrecision::Hours,
[
["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
[" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
[" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
[" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
[" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
],
);
check_all(
OffsetPrecision::Minutes,
[
["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"],
["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"],
[" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"],
[" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"],
["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"],
["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"],
["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"],
["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"],
[" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"],
[" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"],
["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"],
["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"],
],
);
#[rustfmt::skip]
check_all(
OffsetPrecision::Seconds,
[
["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"],
["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
[" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"],
[" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"],
["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"],
["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"],
[" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"],
[" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"],
["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"],
["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"],
],
);
check_all(
OffsetPrecision::OptionalMinutes,
[
["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"],
["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"],
[" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"],
[" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"],
["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"],
["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"],
["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"],
["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"],
[" +345", " -330", "+11", "-11", " +234", "-1235", " +0"],
[" +345", " -330", "+11", "-11", " +234", "-1235", "Z"],
["+345", "-330", "+11", "-11", "+234", "-1235", "+0"],
["+345", "-330", "+11", "-11", "+234", "-1235", "Z"],
],
);
check_all(
OffsetPrecision::OptionalSeconds,
[
["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"],
["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
[" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"],
[" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"],
["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"],
["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"],
[" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"],
[" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"],
["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"],
["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"],
],
);
check_all(
OffsetPrecision::OptionalMinutesAndSeconds,
[
["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"],
["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
[" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"],
[" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"],
["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"],
["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"],
[" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"],
[" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"],
["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"],
["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"],
],
);
}
}

View File

@@ -1,33 +1,103 @@
use pure_rust_locales::{locale_match, Locale};
#[cfg(feature = "unstable-locales")]
mod localized {
use pure_rust_locales::{Locale, locale_match};
pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::ABMON)
pub(crate) const fn default_locale() -> Locale {
Locale::POSIX
}
pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::ABMON)
}
pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::MON)
}
pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::ABDAY)
}
pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::DAY)
}
pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::AM_PM)
}
pub(crate) const fn decimal_point(locale: Locale) -> &'static str {
locale_match!(locale => LC_NUMERIC::DECIMAL_POINT)
}
pub(crate) const fn d_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::D_FMT)
}
pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::D_T_FMT)
}
pub(crate) const fn t_fmt(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::T_FMT)
}
pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str {
locale_match!(locale => LC_TIME::T_FMT_AMPM)
}
}
pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] {
locale_match!(locale => LC_TIME::MON)
#[cfg(feature = "unstable-locales")]
pub(crate) use localized::*;
#[cfg(feature = "unstable-locales")]
pub use pure_rust_locales::Locale;
#[cfg(not(feature = "unstable-locales"))]
mod unlocalized {
#[derive(Copy, Clone, Debug)]
pub(crate) struct Locale;
pub(crate) const fn default_locale() -> Locale {
Locale
}
pub(crate) const fn short_months(_locale: Locale) -> &'static [&'static str] {
&["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
}
pub(crate) const fn long_months(_locale: Locale) -> &'static [&'static str] {
&[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]
}
pub(crate) const fn short_weekdays(_locale: Locale) -> &'static [&'static str] {
&["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
}
pub(crate) const fn long_weekdays(_locale: Locale) -> &'static [&'static str] {
&["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
}
pub(crate) const fn am_pm(_locale: Locale) -> &'static [&'static str] {
&["AM", "PM"]
}
pub(crate) const fn decimal_point(_locale: Locale) -> &'static str {
"."
}
}
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)
}
#[cfg(not(feature = "unstable-locales"))]
pub(crate) use unlocalized::*;

View File

@@ -12,50 +12,70 @@
//! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of
//! the [`Item`](./enum.Item.html) type.
//! They are generated from more readable **format strings**;
//! currently Chrono supports [one built-in syntax closely resembling
//! C's `strftime` format](./strftime/index.html).
//! currently Chrono supports a built-in syntax closely resembling
//! C's `strftime` format. The available options can be found [here](./strftime/index.html).
//!
//! # Example
//! ```
//! # #[cfg(feature = "alloc")] {
//! use chrono::{NaiveDateTime, TimeZone, Utc};
//!
//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap();
//!
//! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S"));
//! assert_eq!(formatted, "2020-11-10 00:01:32");
//!
//! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc();
//! assert_eq!(parsed, date_time);
//! # }
//! # Ok::<(), chrono::ParseError>(())
//! ```
#![allow(ellipsis_inclusive_range_patterns)]
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::fmt;
use core::str::FromStr;
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(any(feature = "alloc", feature = "std", test))]
use naive::{NaiveDate, NaiveTime};
#[cfg(any(feature = "alloc", feature = "std", test))]
use offset::{FixedOffset, Offset};
#[cfg(any(feature = "alloc", feature = "std", test))]
use {Datelike, Timelike};
use {Month, ParseMonthError, ParseWeekdayError, Weekday};
use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday};
#[cfg(feature = "unstable-locales")]
mod formatting;
mod parsed;
// due to the size of parsing routines, they are in separate modules.
mod parse;
pub(crate) mod scan;
pub mod strftime;
#[allow(unused)]
// TODO: remove '#[allow(unused)]' once we use this module for parsing or something else that does
// not require `alloc`.
pub(crate) mod locales;
pub use self::parse::parse;
pub use self::parsed::Parsed;
pub use self::strftime::StrftimeItems;
/// L10n locales.
pub use formatting::SecondsFormat;
pub(crate) use formatting::write_hundreds;
#[cfg(feature = "alloc")]
pub(crate) use formatting::write_rfc2822;
#[cfg(any(feature = "alloc", feature = "serde"))]
pub(crate) use formatting::write_rfc3339;
#[cfg(feature = "alloc")]
#[allow(deprecated)]
pub use formatting::{DelayedFormat, format, format_item};
#[cfg(feature = "unstable-locales")]
pub use pure_rust_locales::Locale;
#[cfg(not(feature = "unstable-locales"))]
#[derive(Debug)]
struct Locale;
pub use locales::Locale;
pub(crate) use parse::parse_rfc3339;
pub use parse::{parse, parse_and_remainder};
pub use parsed::Parsed;
pub use strftime::StrftimeItems;
/// An uninhabited type used for `InternalNumeric` and `InternalFixed` below.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, Hash)]
enum Void {}
/// Padding characters for numeric items.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum Pad {
/// No padding.
None,
@@ -78,10 +98,11 @@ pub enum Pad {
/// It also trims the preceding whitespace if any.
/// It cannot parse the negative number, so some date and time cannot be formatted then
/// parsed with the same formatting items.
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Numeric {
/// Full Gregorian year (FW=4, PW=∞).
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
/// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-).
Year,
/// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year.
YearDiv100,
@@ -94,6 +115,8 @@ pub enum Numeric {
IsoYearDiv100,
/// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
IsoYearMod100,
/// Quarter (FW=PW=1).
Quarter,
/// Month (FW=PW=2).
Month,
/// Day of the month (FW=PW=2).
@@ -134,24 +157,11 @@ pub enum Numeric {
}
/// An opaque type representing numeric item types for internal uses only.
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct InternalNumeric {
_dummy: Void,
}
impl Clone for InternalNumeric {
fn clone(&self) -> Self {
match self._dummy {}
}
}
impl PartialEq for InternalNumeric {
fn eq(&self, _other: &InternalNumeric) -> bool {
match self._dummy {}
}
}
impl Eq for InternalNumeric {}
impl fmt::Debug for InternalNumeric {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<InternalNumeric>")
@@ -162,7 +172,8 @@ impl fmt::Debug for InternalNumeric {
///
/// They have their own rules of formatting and parsing.
/// Otherwise noted, they print in the specified cases but parse case-insensitively.
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Fixed {
/// Abbreviated month names.
///
@@ -208,6 +219,18 @@ pub enum Fixed {
/// The offset is limited from `-24:00` to `+24:00`,
/// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
TimezoneOffsetColon,
/// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`).
///
/// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace.
/// The offset is limited from `-24:00:00` to `+24:00:00`,
/// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
TimezoneOffsetDoubleColon,
/// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`).
///
/// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace.
/// The offset is limited from `-24` to `+24`,
/// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
TimezoneOffsetTripleColon,
/// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`).
///
/// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace,
@@ -234,12 +257,12 @@ pub enum Fixed {
}
/// An opaque type representing fixed-format item types for internal uses only.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InternalFixed {
val: InternalInternal,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum InternalInternal {
/// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but
/// allows missing minutes (per [ISO 8601][iso8601]).
@@ -258,18 +281,63 @@ enum InternalInternal {
Nanosecond9NoDot,
}
/// Type for specifying the format of UTC offsets.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct OffsetFormat {
/// See `OffsetPrecision`.
pub precision: OffsetPrecision,
/// Separator between hours, minutes and seconds.
pub colons: Colons,
/// Represent `+00:00` as `Z`.
pub allow_zulu: bool,
/// Pad the hour value to two digits.
pub padding: Pad,
}
/// The precision of an offset from UTC formatting item.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum OffsetPrecision {
/// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to
/// have an offset of 30 minutes, 15 minutes, etc.
/// Any minutes and seconds get truncated.
Hours,
/// Format offset from UTC as hours and minutes.
/// Any seconds will be rounded to the nearest minute.
Minutes,
/// Format offset from UTC as hours, minutes and seconds.
Seconds,
/// Format offset from UTC as hours, and optionally with minutes.
/// Any seconds will be rounded to the nearest minute.
OptionalMinutes,
/// Format offset from UTC as hours and minutes, and optionally seconds.
OptionalSeconds,
/// Format offset from UTC as hours and optionally minutes and seconds.
OptionalMinutesAndSeconds,
}
/// The separator between hours and minutes in an offset.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Colons {
/// No separator
None,
/// Colon (`:`) as separator
Colon,
/// No separator when formatting, colon allowed when parsing.
Maybe,
}
/// A single formatting item. This is used for both formatting and parsing.
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Item<'a> {
/// A literally printed and parsed text.
Literal(&'a str),
/// Same as `Literal` but with the string owned by the item.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg(feature = "alloc")]
OwnedLiteral(Box<str>),
/// Whitespace. Prints literally but reads zero or more whitespace.
Space(&'a str),
/// Same as `Space` but with the string owned by the item.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg(feature = "alloc")]
OwnedSpace(Box<str>),
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
/// the parser simply ignores any padded whitespace and zeroes.
@@ -280,49 +348,57 @@ pub enum Item<'a> {
Error,
}
macro_rules! lit {
($x:expr) => {
Item::Literal($x)
};
const fn num(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::None)
}
macro_rules! sp {
($x:expr) => {
Item::Space($x)
};
const fn num0(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Zero)
}
macro_rules! num {
($x:ident) => {
Item::Numeric(Numeric::$x, Pad::None)
};
const fn nums(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Space)
}
macro_rules! num0 {
($x:ident) => {
Item::Numeric(Numeric::$x, Pad::Zero)
};
const fn fixed(fixed: Fixed) -> Item<'static> {
Item::Fixed(fixed)
}
macro_rules! nums {
($x:ident) => {
Item::Numeric(Numeric::$x, Pad::Space)
};
const fn internal_fixed(val: InternalInternal) -> Item<'static> {
Item::Fixed(Fixed::Internal(InternalFixed { val }))
}
macro_rules! fix {
($x:ident) => {
Item::Fixed(Fixed::$x)
};
}
macro_rules! internal_fix {
($x:ident) => {
Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x }))
};
impl Item<'_> {
/// Convert items that contain a reference to the format string into an owned variant.
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn to_owned(self) -> Item<'static> {
match self {
Item::Literal(s) => Item::OwnedLiteral(Box::from(s)),
Item::Space(s) => Item::OwnedSpace(Box::from(s)),
Item::Numeric(n, p) => Item::Numeric(n, p),
Item::Fixed(f) => Item::Fixed(f),
Item::OwnedLiteral(l) => Item::OwnedLiteral(l),
Item::OwnedSpace(s) => Item::OwnedSpace(s),
Item::Error => Item::Error,
}
}
}
/// An error from the `parse` function.
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
pub struct ParseError(ParseErrorKind);
impl ParseError {
/// The category of parse error
pub const fn kind(&self) -> ParseErrorKind {
self.0
}
}
/// The category of parse error
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
enum ParseErrorKind {
#[allow(clippy::manual_non_exhaustive)]
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
pub enum ParseErrorKind {
/// Given field is out of permitted range.
OutOfRange,
@@ -348,8 +424,12 @@ enum ParseErrorKind {
/// All formatting items have been read but there is a remaining input.
TooLong,
/// There was an error on the formatting string, or there were non-supported formating items.
/// There was an error on the formatting string, or there were non-supported formatting items.
BadFormat,
// TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release.
#[doc(hidden)]
__Nonexhaustive,
}
/// Same as `Result<T, ParseError>`.
@@ -365,11 +445,12 @@ impl fmt::Display for ParseError {
ParseErrorKind::TooShort => write!(f, "premature end of input"),
ParseErrorKind::TooLong => write!(f, "trailing input"),
ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"),
_ => unreachable!(),
}
}
}
#[cfg(any(feature = "std", test))]
#[cfg(feature = "std")]
impl Error for ParseError {
#[allow(deprecated)]
fn description(&self) -> &str {
@@ -378,465 +459,40 @@ impl Error for ParseError {
}
// to be used in this module and submodules
const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible);
const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough);
const INVALID: ParseError = ParseError(ParseErrorKind::Invalid);
const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort);
const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
/// Formats single formatting item
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn format_item<'a>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
item: &Item<'a>,
) -> fmt::Result {
let mut result = String::new();
format_inner(&mut result, date, time, off, item, None)?;
w.pad(&result)
}
#[cfg(any(feature = "alloc", feature = "std", test))]
fn format_inner<'a>(
result: &mut String,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
item: &Item<'a>,
_locale: Option<Locale>,
) -> fmt::Result {
#[cfg(feature = "unstable-locales")]
let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
let locale = _locale.unwrap_or(Locale::POSIX);
let am_pm = locales::am_pm(locale);
(
locales::short_months(locale),
locales::long_months(locale),
locales::short_weekdays(locale),
locales::long_weekdays(locale),
am_pm,
&[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()],
)
};
#[cfg(not(feature = "unstable-locales"))]
let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
(
&["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
&[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
&["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
&["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
&["AM", "PM"],
&["am", "pm"],
)
};
use core::fmt::Write;
use div::{div_floor, mod_floor};
match *item {
Item::Literal(s) | Item::Space(s) => result.push_str(s),
#[cfg(any(feature = "alloc", feature = "std", test))]
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
Item::Numeric(ref spec, ref pad) => {
use self::Numeric::*;
let week_from_sun = |d: &NaiveDate| {
(d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7
};
let week_from_mon = |d: &NaiveDate| {
(d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7
};
let (width, v) = match *spec {
Year => (4, date.map(|d| i64::from(d.year()))),
YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))),
YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))),
IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))),
IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))),
Month => (2, date.map(|d| i64::from(d.month()))),
Day => (2, date.map(|d| i64::from(d.day()))),
WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
Hour => (2, time.map(|t| i64::from(t.hour()))),
Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
Minute => (2, time.map(|t| i64::from(t.minute()))),
Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
Timestamp => (
1,
match (date, time, off) {
(Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
(Some(d), Some(t), Some(&(_, off))) => {
Some((d.and_time(*t) - off).timestamp())
}
(_, _, _) => None,
},
),
// for the future expansion
Internal(ref int) => match int._dummy {},
};
if let Some(v) = v {
if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) {
// non-four-digit years require an explicit sign as per ISO 8601
match *pad {
Pad::None => write!(result, "{:+}", v),
Pad::Zero => write!(result, "{:+01$}", v, width + 1),
Pad::Space => write!(result, "{:+1$}", v, width + 1),
}
} else {
match *pad {
Pad::None => write!(result, "{}", v),
Pad::Zero => write!(result, "{:01$}", v, width),
Pad::Space => write!(result, "{:1$}", v, width),
}
}?
} else {
return Err(fmt::Error); // insufficient arguments for given format
}
}
Item::Fixed(ref spec) => {
use self::Fixed::*;
/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`.
/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true.
fn write_local_minus_utc(
result: &mut String,
off: FixedOffset,
allow_zulu: bool,
use_colon: bool,
) -> fmt::Result {
let off = off.local_minus_utc();
if !allow_zulu || off != 0 {
let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
if use_colon {
write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60)
} else {
write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60)
}
} else {
result.push_str("Z");
Ok(())
}
}
let ret =
match *spec {
ShortMonthName => date.map(|d| {
result.push_str(short_months[d.month0() as usize]);
Ok(())
}),
LongMonthName => date.map(|d| {
result.push_str(long_months[d.month0() as usize]);
Ok(())
}),
ShortWeekdayName => date.map(|d| {
result
.push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]);
Ok(())
}),
LongWeekdayName => date.map(|d| {
result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]);
Ok(())
}),
LowerAmPm => time.map(|t| {
#[cfg_attr(feature = "cargo-clippy", allow(useless_asref))]
{
result.push_str(if t.hour12().0 {
am_pm_lowercase[1].as_ref()
} else {
am_pm_lowercase[0].as_ref()
});
}
Ok(())
}),
UpperAmPm => time.map(|t| {
result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] });
Ok(())
}),
Nanosecond => time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
if nano == 0 {
Ok(())
} else if nano % 1_000_000 == 0 {
write!(result, ".{:03}", nano / 1_000_000)
} else if nano % 1_000 == 0 {
write!(result, ".{:06}", nano / 1_000)
} else {
write!(result, ".{:09}", nano)
}
}),
Nanosecond3 => time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:03}", nano / 1_000_000)
}),
Nanosecond6 => time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:06}", nano / 1_000)
}),
Nanosecond9 => time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:09}", nano)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time
.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:03}", nano / 1_000_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time
.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:06}", nano / 1_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time
.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:09}", nano)
}),
TimezoneName => off.map(|&(ref name, _)| {
result.push_str(name);
Ok(())
}),
TimezoneOffsetColon => {
off.map(|&(_, off)| write_local_minus_utc(result, off, false, true))
}
TimezoneOffsetColonZ => {
off.map(|&(_, off)| write_local_minus_utc(result, off, true, true))
}
TimezoneOffset => {
off.map(|&(_, off)| write_local_minus_utc(result, off, false, false))
}
TimezoneOffsetZ => {
off.map(|&(_, off)| write_local_minus_utc(result, off, true, false))
}
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
panic!("Do not try to write %#z it is undefined")
}
RFC2822 =>
// same as `%a, %d %b %Y %H:%M:%S %z`
{
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
let sec = t.second() + t.nanosecond() / 1_000_000_000;
write!(
result,
"{}, {:02} {} {:04} {:02}:{:02}:{:02} ",
short_weekdays[d.weekday().num_days_from_sunday() as usize],
d.day(),
short_months[d.month0() as usize],
d.year(),
t.hour(),
t.minute(),
sec
)?;
Some(write_local_minus_utc(result, off, false, false))
} else {
None
}
}
RFC3339 =>
// same as `%Y-%m-%dT%H:%M:%S%.f%:z`
{
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
// reuse `Debug` impls which already print ISO 8601 format.
// this is faster in this way.
write!(result, "{:?}T{:?}", d, t)?;
Some(write_local_minus_utc(result, off, false, true))
} else {
None
}
}
};
match ret {
Some(ret) => ret?,
None => return Err(fmt::Error), // insufficient arguments for given format
}
}
Item::Error => return Err(fmt::Error),
}
Ok(())
}
/// Tries to format given arguments with given formatting items.
/// Internally used by `DelayedFormat`.
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn format<'a, I, B>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
items: I,
) -> fmt::Result
where
I: Iterator<Item = B> + Clone,
B: Borrow<Item<'a>>,
{
let mut result = String::new();
for item in items {
format_inner(&mut result, date, time, off, item.borrow(), None)?;
}
w.pad(&result)
}
mod parsed;
// due to the size of parsing routines, they are in separate modules.
mod parse;
mod scan;
pub mod strftime;
/// A *temporary* object which can be used as an argument to `format!` or others.
/// This is normally constructed via `format` methods of each date and time type.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[derive(Debug)]
pub struct DelayedFormat<I> {
/// The date view, if any.
date: Option<NaiveDate>,
/// The time view, if any.
time: Option<NaiveTime>,
/// The name and local-to-UTC difference for the offset (timezone), if any.
off: Option<(String, FixedOffset)>,
/// An iterator returning formatting items.
items: I,
/// Locale used for text.
locale: Option<Locale>,
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
/// Makes a new `DelayedFormat` value out of local date and time.
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
DelayedFormat { date: date, time: time, off: None, items: items, locale: None }
}
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
pub fn new_with_offset<Off>(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
offset: &Off,
items: I,
) -> DelayedFormat<I>
where
Off: Offset + fmt::Display,
{
let name_and_diff = (offset.to_string(), offset.fix());
DelayedFormat {
date: date,
time: time,
off: Some(name_and_diff),
items: items,
locale: None,
}
}
/// Makes a new `DelayedFormat` value out of local date and time and locale.
#[cfg(feature = "unstable-locales")]
pub fn new_with_locale(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
items: I,
locale: Locale,
) -> DelayedFormat<I> {
DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) }
}
/// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
#[cfg(feature = "unstable-locales")]
pub fn new_with_offset_and_locale<Off>(
date: Option<NaiveDate>,
time: Option<NaiveTime>,
offset: &Off,
items: I,
locale: Locale,
) -> DelayedFormat<I>
where
Off: Offset + fmt::Display,
{
let name_and_diff = (offset.to_string(), offset.fix());
DelayedFormat {
date: date,
time: time,
off: Some(name_and_diff),
items: items,
locale: Some(locale),
}
}
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[cfg(feature = "unstable-locales")]
{
if let Some(locale) = self.locale {
return format_localized(
f,
self.date.as_ref(),
self.time.as_ref(),
self.off.as_ref(),
self.items.clone(),
locale,
);
}
}
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
}
}
// this implementation is here only because we need some private code from `scan`
/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html).
/// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html).
///
/// # Example
///
/// ~~~~
/// ```
/// use chrono::Weekday;
///
/// assert_eq!("Sunday".parse::<Weekday>(), Ok(Weekday::Sun));
/// assert!("any day".parse::<Weekday>().is_err());
/// ~~~~
/// ```
///
/// The parsing is case-insensitive.
///
/// ~~~~
/// ```
/// # use chrono::Weekday;
/// assert_eq!("mON".parse::<Weekday>(), Ok(Weekday::Mon));
/// ~~~~
/// ```
///
/// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted.
///
/// ~~~~
/// ```
/// # use chrono::Weekday;
/// assert!("thurs".parse::<Weekday>().is_err());
/// ~~~~
/// ```
impl FromStr for Weekday {
type Err = ParseWeekdayError;
@@ -849,68 +505,31 @@ impl FromStr for Weekday {
}
}
/// Formats single formatting item
#[cfg(feature = "unstable-locales")]
pub fn format_item_localized<'a>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
item: &Item<'a>,
locale: Locale,
) -> fmt::Result {
let mut result = String::new();
format_inner(&mut result, date, time, off, item, Some(locale))?;
w.pad(&result)
}
/// Tries to format given arguments with given formatting items.
/// Internally used by `DelayedFormat`.
#[cfg(feature = "unstable-locales")]
pub fn format_localized<'a, I, B>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
items: I,
locale: Locale,
) -> fmt::Result
where
I: Iterator<Item = B> + Clone,
B: Borrow<Item<'a>>,
{
let mut result = String::new();
for item in items {
format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
}
w.pad(&result)
}
/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html).
/// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html).
///
/// # Example
///
/// ~~~~
/// ```
/// use chrono::Month;
///
/// assert_eq!("January".parse::<Month>(), Ok(Month::January));
/// assert!("any day".parse::<Month>().is_err());
/// ~~~~
/// ```
///
/// The parsing is case-insensitive.
///
/// ~~~~
/// ```
/// # use chrono::Month;
/// assert_eq!("fEbruARy".parse::<Month>(), Ok(Month::February));
/// ~~~~
/// ```
///
/// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted.
///
/// ~~~~
/// ```
/// # use chrono::Month;
/// assert!("septem".parse::<Month>().is_err());
/// assert!("Augustin".parse::<Month>().is_err());
/// ~~~~
/// ```
impl FromStr for Month {
type Err = ParseMonthError;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,28 +5,8 @@
* Various scanning routines for the parser.
*/
#![allow(deprecated)]
use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
use Weekday;
/// Returns true when two slices are equal case-insensitively (in ASCII).
/// Assumes that the `pattern` is already converted to lower case.
fn equals(s: &str, pattern: &str) -> bool {
let mut xs = s.as_bytes().iter().map(|&c| match c {
b'A'...b'Z' => c + 32,
_ => c,
});
let mut ys = pattern.as_bytes().iter().cloned();
loop {
match (xs.next(), ys.next()) {
(None, None) => return true,
(None, _) | (_, None) => return false,
(Some(x), Some(y)) if x != y => return false,
_ => (),
}
}
}
use super::{INVALID, OUT_OF_RANGE, ParseResult, TOO_SHORT};
use crate::Weekday;
/// Tries to parse the non-negative number from `min` to `max` digits.
///
@@ -34,7 +14,7 @@ fn equals(s: &str, pattern: &str) -> bool {
/// More than `max` digits are consumed up to the first `max` digits.
/// Any number that does not fit in `i64` is an error.
#[inline]
pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
assert!(min <= max);
// We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
@@ -48,7 +28,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
let mut n = 0i64;
for (i, c) in bytes.iter().take(max).cloned().enumerate() {
// cloned() = copied()
if c < b'0' || b'9' < c {
if !c.is_ascii_digit() {
if i < min {
return Err(INVALID);
} else {
@@ -62,12 +42,12 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
};
}
Ok((&s[::core::cmp::min(max, bytes.len())..], n))
Ok((&s[core::cmp::min(max, bytes.len())..], n))
}
/// Tries to consume at least one digits as a fractional second.
/// Returns the number of whole nanoseconds (0--999,999,999).
pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
// record the number of digits consumed for later scaling.
let origlen = s.len();
let (s, v) = number(s, 1, 9)?;
@@ -79,14 +59,14 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;
// if there are more than 9 digits, skip next digits.
let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
let s = s.trim_start_matches(|c: char| c.is_ascii_digit());
Ok((s, v))
}
/// Tries to consume a fixed number of digits as a fractional second.
/// Returns the number of whole nanoseconds (0--999,999,999).
pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
// record the number of digits consumed for later scaling.
let (s, v) = number(s, digits, digits)?;
@@ -99,7 +79,7 @@ pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
}
/// Tries to parse the month index (0 through 11) with the first three ASCII letters.
pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
if s.len() < 3 {
return Err(TOO_SHORT);
}
@@ -123,7 +103,7 @@ pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
}
/// Tries to parse the weekday with the first three ASCII letters.
pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
if s.len() < 3 {
return Err(TOO_SHORT);
}
@@ -143,16 +123,18 @@ pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
/// Tries to parse the month index (0 through 11) with short or long month names.
/// It prefers long month names to short month names when both are possible.
pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
// lowercased month names, minus first three chars
static LONG_MONTH_SUFFIXES: [&'static str; 12] =
["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [
b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember",
b"ember",
];
let (mut s, month0) = short_month0(s)?;
// tries to consume the suffix if possible
let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) {
s = &s[suffix.len()..];
}
@@ -161,16 +143,16 @@ pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
/// Tries to parse the weekday with short or long weekday names.
/// It prefers long weekday names to short weekday names when both are possible.
pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
// lowercased weekday names, minus first three chars
static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] =
["day", "sday", "nesday", "rsday", "day", "urday", "day"];
static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] =
[b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"];
let (mut s, weekday) = short_weekday(s)?;
// tries to consume the suffix if possible
let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) {
s = &s[suffix.len()..];
}
@@ -178,7 +160,7 @@ pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
}
/// Tries to consume exactly one given character.
pub fn char(s: &str, c1: u8) -> ParseResult<&str> {
pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> {
match s.as_bytes().first() {
Some(&c) if c == c1 => Ok(&s[1..]),
Some(_) => Err(INVALID),
@@ -187,8 +169,8 @@ pub fn char(s: &str, c1: u8) -> ParseResult<&str> {
}
/// Tries to consume one or more whitespace.
pub fn space(s: &str) -> ParseResult<&str> {
let s_ = s.trim_left();
pub(super) fn space(s: &str) -> ParseResult<&str> {
let s_ = s.trim_start();
if s_.len() < s.len() {
Ok(s_)
} else if s.is_empty() {
@@ -199,48 +181,73 @@ pub fn space(s: &str) -> ParseResult<&str> {
}
/// Consumes any number (including zero) of colon or spaces.
pub fn colon_or_space(s: &str) -> ParseResult<&str> {
Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> {
Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace()))
}
/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
/// Parse a timezone from `s` and return the offset in seconds.
///
/// The additional `colon` may be used to parse a mandatory or optional `:`
/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
pub fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
timezone_offset_internal(s, consume_colon, false)
}
fn timezone_offset_internal<F>(
/// The `consume_colon` function is used to parse a mandatory or optional `:`
/// separator between hours offset and minutes offset.
///
/// The `allow_missing_minutes` flag allows the timezone minutes offset to be
/// missing from `s`.
///
/// The `allow_tz_minus_sign` flag allows the timezone offset negative character
/// to also be `` MINUS SIGN (U+2212) in addition to the typical
/// ASCII-compatible `-` HYPHEN-MINUS (U+2D).
/// This is part of [RFC 3339 & ISO 8601].
///
/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC
pub(crate) fn timezone_offset<F>(
mut s: &str,
mut consume_colon: F,
allow_zulu: bool,
allow_missing_minutes: bool,
allow_tz_minus_sign: bool,
) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
fn digits(s: &str) -> ParseResult<(u8, u8)> {
let b = s.as_bytes();
if b.len() < 2 {
Err(TOO_SHORT)
} else {
Ok((b[0], b[1]))
if allow_zulu {
if let Some(&b'Z' | &b'z') = s.as_bytes().first() {
return Ok((&s[1..], 0));
}
}
let negative = match s.as_bytes().first() {
Some(&b'+') => false,
Some(&b'-') => true,
const fn digits(s: &str) -> ParseResult<(u8, u8)> {
let b = s.as_bytes();
if b.len() < 2 { Err(TOO_SHORT) } else { Ok((b[0], b[1])) }
}
let negative = match s.chars().next() {
Some('+') => {
// PLUS SIGN (U+2B)
s = &s['+'.len_utf8()..];
false
}
Some('-') => {
// HYPHEN-MINUS (U+2D)
s = &s['-'.len_utf8()..];
true
}
Some('') => {
// MINUS SIGN (U+2212)
if !allow_tz_minus_sign {
return Err(INVALID);
}
s = &s[''.len_utf8()..];
true
}
Some(_) => return Err(INVALID),
None => return Err(TOO_SHORT),
};
s = &s[1..];
// hours (00--99)
let hours = match digits(s)? {
(h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
_ => return Err(INVALID),
};
s = &s[2..];
@@ -252,8 +259,8 @@ where
// if the next two items are digits then we have to add minutes
let minutes = if let Ok(ds) = digits(s) {
match ds {
(m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
(b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE),
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
(b'6'..=b'9', b'0'..=b'9') => return Err(OUT_OF_RANGE),
_ => return Err(INVALID),
}
} else if allow_missing_minutes {
@@ -263,7 +270,7 @@ where
};
s = match s.len() {
len if len >= 2 => &s[2..],
len if len == 0 => s,
0 => s,
_ => return Err(TOO_SHORT),
};
@@ -271,80 +278,155 @@ where
Ok((s, if negative { -seconds } else { seconds }))
}
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`.
pub fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
{
let bytes = s.as_bytes();
match bytes.first() {
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
Some(&b'u') | Some(&b'U') => {
if bytes.len() >= 3 {
let (b, c) = (bytes[1], bytes[2]);
match (b | 32, c | 32) {
(b't', b'c') => Ok((&s[3..], 0)),
_ => Err(INVALID),
}
} else {
Err(INVALID)
}
}
_ => 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 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 fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> {
/// See [RFC 2822 Section 4.3].
///
/// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3
pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> {
// tries to parse legacy time zone names
let upto = s
.as_bytes()
.iter()
.position(|&c| match c {
b'a'...b'z' | b'A'...b'Z' => false,
_ => true,
})
.unwrap_or_else(|| s.len());
let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len());
if upto > 0 {
let name = &s[..upto];
let name = &s.as_bytes()[..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
let offset_hours = |o| Ok((s, o * 3600));
// RFC 2822 requires support for some named North America timezones, a small subset of all
// named timezones.
if name.eq_ignore_ascii_case(b"gmt")
|| name.eq_ignore_ascii_case(b"ut")
|| name.eq_ignore_ascii_case(b"z")
{
return offset_hours(0);
} else if name.eq_ignore_ascii_case(b"edt") {
return offset_hours(-4);
} else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") {
return offset_hours(-5);
} else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") {
return offset_hours(-6);
} else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") {
return offset_hours(-7);
} else if name.eq_ignore_ascii_case(b"pst") {
return offset_hours(-8);
} else if name.len() == 1 {
if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] {
// recommended by RFC 2822: consume but treat it as -0000
return Ok((s, 0));
}
}
Err(INVALID)
} else {
let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
Ok((s_, Some(offset)))
timezone_offset(s, |s| Ok(s), false, false, false)
}
}
/// Tries to consume everyting until next whitespace-like symbol.
/// Does not provide any offset information from the consumed data.
pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
/// Tries to consume an RFC2822 comment including preceding ` `.
///
/// Returns the remaining string after the closing parenthesis.
pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> {
use CommentState::*;
let s = s.trim_start();
let mut state = Start;
for (i, c) in s.bytes().enumerate() {
state = match (state, c) {
(Start, b'(') => Next(1),
(Next(1), b')') => return Ok((&s[i + 1..], ())),
(Next(depth), b'\\') => Escape(depth),
(Next(depth), b'(') => Next(depth + 1),
(Next(depth), b')') => Next(depth - 1),
(Next(depth), _) | (Escape(depth), _) => Next(depth),
_ => return Err(INVALID),
};
}
Err(TOO_SHORT)
}
enum CommentState {
Start,
Next(usize),
Escape(usize),
}
#[cfg(test)]
mod tests {
use super::{
comment_2822, nanosecond, nanosecond_fixed, short_or_long_month0, short_or_long_weekday,
timezone_offset_2822,
};
use crate::Weekday;
use crate::format::{INVALID, TOO_SHORT};
#[test]
fn test_rfc2822_comments() {
let testdata = [
("", Err(TOO_SHORT)),
(" ", Err(TOO_SHORT)),
("x", Err(INVALID)),
("(", Err(TOO_SHORT)),
("()", Ok("")),
(" \r\n\t()", Ok("")),
("() ", Ok(" ")),
("()z", Ok("z")),
("(x)", Ok("")),
("(())", Ok("")),
("((()))", Ok("")),
("(x(x(x)x)x)", Ok("")),
("( x ( x ( x ) x ) x )", Ok("")),
(r"(\)", Err(TOO_SHORT)),
(r"(\()", Ok("")),
(r"(\))", Ok("")),
(r"(\\)", Ok("")),
("(()())", Ok("")),
("( x ( x ) x ( x ) x )", Ok("")),
];
for (test_in, expected) in testdata.iter() {
let actual = comment_2822(test_in).map(|(s, _)| s);
assert_eq!(
*expected, actual,
"{:?} expected to produce {:?}, but produced {:?}.",
test_in, expected, actual
);
}
}
#[test]
fn test_timezone_offset_2822() {
assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", -21600));
assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", -28800));
assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", -25200));
assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", -57060));
assert_eq!(timezone_offset_2822("Gp"), Err(INVALID));
}
#[test]
fn test_short_or_long_month0() {
assert_eq!(short_or_long_month0("JUn").unwrap(), ("", 5));
assert_eq!(short_or_long_month0("mAy").unwrap(), ("", 4));
assert_eq!(short_or_long_month0("AuG").unwrap(), ("", 7));
assert_eq!(short_or_long_month0("Aprâ").unwrap(), ("â", 3));
assert_eq!(short_or_long_month0("JUl").unwrap(), ("", 6));
assert_eq!(short_or_long_month0("mAr").unwrap(), ("", 2));
assert_eq!(short_or_long_month0("Jan").unwrap(), ("", 0));
}
#[test]
fn test_short_or_long_weekday() {
assert_eq!(short_or_long_weekday("sAtu").unwrap(), ("u", Weekday::Sat));
assert_eq!(short_or_long_weekday("thu").unwrap(), ("", Weekday::Thu));
}
#[test]
fn test_nanosecond_fixed() {
assert_eq!(nanosecond_fixed("", 0usize).unwrap(), ("", 0));
assert!(nanosecond_fixed("", 1usize).is_err());
}
#[test]
fn test_nanosecond() {
assert_eq!(nanosecond("").unwrap(), ("Ù", 200000000));
assert_eq!(nanosecond("8").unwrap(), ("", 800000000));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

472
third_party/rust/chrono/src/month.rs vendored Normal file
View File

@@ -0,0 +1,472 @@
use core::fmt;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use crate::OutOfRange;
use crate::naive::NaiveDate;
/// The month of the year.
///
/// This enum is just a convenience implementation.
/// The month in dates created by DateLike objects does not return this enum.
///
/// It is possible to convert from a date to a month independently
/// ```
/// use chrono::prelude::*;
/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
/// // `2019-10-28T09:10:11Z`
/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok();
/// assert_eq!(month, Some(Month::October))
/// ```
/// Or from a Month to an integer usable by dates
/// ```
/// # use chrono::prelude::*;
/// let month = Month::January;
/// let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
/// ```
/// Allows mapping from and to month, from 1-January to 12-December.
/// Can be Serialized/Deserialized with serde
// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior.
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq, PartialOrd)),
archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
pub enum Month {
/// January
January = 0,
/// February
February = 1,
/// March
March = 2,
/// April
April = 3,
/// May
May = 4,
/// June
June = 5,
/// July
July = 6,
/// August
August = 7,
/// September
September = 8,
/// October
October = 9,
/// November
November = 10,
/// December
December = 11,
}
impl Month {
/// The next month.
///
/// `m`: | `January` | `February` | `...` | `December`
/// ----------- | --------- | ---------- | --- | ---------
/// `m.succ()`: | `February` | `March` | `...` | `January`
#[inline]
#[must_use]
pub const fn succ(&self) -> Month {
match *self {
Month::January => Month::February,
Month::February => Month::March,
Month::March => Month::April,
Month::April => Month::May,
Month::May => Month::June,
Month::June => Month::July,
Month::July => Month::August,
Month::August => Month::September,
Month::September => Month::October,
Month::October => Month::November,
Month::November => Month::December,
Month::December => Month::January,
}
}
/// The previous month.
///
/// `m`: | `January` | `February` | `...` | `December`
/// ----------- | --------- | ---------- | --- | ---------
/// `m.pred()`: | `December` | `January` | `...` | `November`
#[inline]
#[must_use]
pub const fn pred(&self) -> Month {
match *self {
Month::January => Month::December,
Month::February => Month::January,
Month::March => Month::February,
Month::April => Month::March,
Month::May => Month::April,
Month::June => Month::May,
Month::July => Month::June,
Month::August => Month::July,
Month::September => Month::August,
Month::October => Month::September,
Month::November => Month::October,
Month::December => Month::November,
}
}
/// Returns a month-of-year number starting from January = 1.
///
/// `m`: | `January` | `February` | `...` | `December`
/// -------------------------| --------- | ---------- | --- | -----
/// `m.number_from_month()`: | 1 | 2 | `...` | 12
#[inline]
#[must_use]
pub const fn number_from_month(&self) -> u32 {
match *self {
Month::January => 1,
Month::February => 2,
Month::March => 3,
Month::April => 4,
Month::May => 5,
Month::June => 6,
Month::July => 7,
Month::August => 8,
Month::September => 9,
Month::October => 10,
Month::November => 11,
Month::December => 12,
}
}
/// Get the name of the month
///
/// ```
/// use chrono::Month;
///
/// assert_eq!(Month::January.name(), "January")
/// ```
#[must_use]
pub const fn name(&self) -> &'static str {
match *self {
Month::January => "January",
Month::February => "February",
Month::March => "March",
Month::April => "April",
Month::May => "May",
Month::June => "June",
Month::July => "July",
Month::August => "August",
Month::September => "September",
Month::October => "October",
Month::November => "November",
Month::December => "December",
}
}
/// Get the length in days of the month
///
/// Yields `None` if `year` is out of range for `NaiveDate`.
pub fn num_days(&self, year: i32) -> Option<u8> {
Some(match *self {
Month::January => 31,
Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() {
true => 29,
false => 28,
},
Month::March => 31,
Month::April => 30,
Month::May => 31,
Month::June => 30,
Month::July => 31,
Month::August => 31,
Month::September => 30,
Month::October => 31,
Month::November => 30,
Month::December => 31,
})
}
}
impl TryFrom<u8> for Month {
type Error = OutOfRange;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Month::January),
2 => Ok(Month::February),
3 => Ok(Month::March),
4 => Ok(Month::April),
5 => Ok(Month::May),
6 => Ok(Month::June),
7 => Ok(Month::July),
8 => Ok(Month::August),
9 => Ok(Month::September),
10 => Ok(Month::October),
11 => Ok(Month::November),
12 => Ok(Month::December),
_ => Err(OutOfRange::new()),
}
}
}
impl num_traits::FromPrimitive for Month {
/// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1.
///
/// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12`
/// ---------------------------| -------------------- | --------------------- | ... | -----
/// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December)
#[inline]
fn from_u64(n: u64) -> Option<Month> {
Self::from_u32(n as u32)
}
#[inline]
fn from_i64(n: i64) -> Option<Month> {
Self::from_u32(n as u32)
}
#[inline]
fn from_u32(n: u32) -> Option<Month> {
match n {
1 => Some(Month::January),
2 => Some(Month::February),
3 => Some(Month::March),
4 => Some(Month::April),
5 => Some(Month::May),
6 => Some(Month::June),
7 => Some(Month::July),
8 => Some(Month::August),
9 => Some(Month::September),
10 => Some(Month::October),
11 => Some(Month::November),
12 => Some(Month::December),
_ => None,
}
}
}
/// A duration in calendar months
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
pub struct Months(pub(crate) u32);
impl Months {
/// Construct a new `Months` from a number of months
pub const fn new(num: u32) -> Self {
Self(num)
}
/// Returns the total number of months in the `Months` instance.
#[inline]
pub const fn as_u32(&self) -> u32 {
self.0
}
}
/// An error resulting from reading `<Month>` value with `FromStr`.
#[derive(Clone, PartialEq, Eq)]
pub struct ParseMonthError {
pub(crate) _dummy: (),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseMonthError {}
impl fmt::Display for ParseMonthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ParseMonthError {{ .. }}")
}
}
impl fmt::Debug for ParseMonthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ParseMonthError {{ .. }}")
}
}
#[cfg(feature = "serde")]
mod month_serde {
use super::Month;
use serde::{de, ser};
use core::fmt;
impl ser::Serialize for Month {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(self.name())
}
}
struct MonthVisitor;
impl de::Visitor<'_> for MonthVisitor {
type Value = Month;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Month")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected"))
}
}
impl<'de> de::Deserialize<'de> for Month {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(MonthVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::Month;
use crate::{Datelike, Months, OutOfRange, TimeZone, Utc};
#[test]
fn test_month_enum_try_from() {
assert_eq!(Month::try_from(1), Ok(Month::January));
assert_eq!(Month::try_from(2), Ok(Month::February));
assert_eq!(Month::try_from(12), Ok(Month::December));
assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
let month = Month::January;
let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
}
#[test]
fn test_month_enum_primitive_parse() {
use num_traits::FromPrimitive;
let jan_opt = Month::from_u32(1);
let feb_opt = Month::from_u64(2);
let dec_opt = Month::from_i64(12);
let no_month = Month::from_u32(13);
assert_eq!(jan_opt, Some(Month::January));
assert_eq!(feb_opt, Some(Month::February));
assert_eq!(dec_opt, Some(Month::December));
assert_eq!(no_month, None);
let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
assert_eq!(Month::from_u32(date.month()), Some(Month::October));
let month = Month::January;
let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
}
#[test]
fn test_month_enum_succ_pred() {
assert_eq!(Month::January.succ(), Month::February);
assert_eq!(Month::December.succ(), Month::January);
assert_eq!(Month::January.pred(), Month::December);
assert_eq!(Month::February.pred(), Month::January);
}
#[test]
fn test_month_partial_ord() {
assert!(Month::January <= Month::January);
assert!(Month::January < Month::February);
assert!(Month::January < Month::December);
assert!(Month::July >= Month::May);
assert!(Month::September > Month::March);
}
#[test]
fn test_months_as_u32() {
assert_eq!(Months::new(0).as_u32(), 0);
assert_eq!(Months::new(1).as_u32(), 1);
assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX);
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_serialize() {
use Month::*;
use serde_json::to_string;
let cases: Vec<(Month, &str)> = vec![
(January, "\"January\""),
(February, "\"February\""),
(March, "\"March\""),
(April, "\"April\""),
(May, "\"May\""),
(June, "\"June\""),
(July, "\"July\""),
(August, "\"August\""),
(September, "\"September\""),
(October, "\"October\""),
(November, "\"November\""),
(December, "\"December\""),
];
for (month, expected_str) in cases {
let string = to_string(&month).unwrap();
assert_eq!(string, expected_str);
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_deserialize() {
use Month::*;
use serde_json::from_str;
let cases: Vec<(&str, Month)> = vec![
("\"january\"", January),
("\"jan\"", January),
("\"FeB\"", February),
("\"MAR\"", March),
("\"mar\"", March),
("\"april\"", April),
("\"may\"", May),
("\"june\"", June),
("\"JULY\"", July),
("\"august\"", August),
("\"september\"", September),
("\"October\"", October),
("\"November\"", November),
("\"DECEmbEr\"", December),
];
for (string, expected_month) in cases {
let month = from_str::<Month>(string).unwrap();
assert_eq!(month, expected_month);
}
let errors: Vec<&str> =
vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
for string in errors {
from_str::<Month>(string).unwrap_err();
}
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let month = Month::January;
let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap();
assert_eq!(rkyv::from_bytes::<Month>(&bytes).unwrap(), month);
}
#[test]
fn num_days() {
assert_eq!(Month::January.num_days(2020), Some(31));
assert_eq!(Month::February.num_days(2020), Some(29));
assert_eq!(Month::February.num_days(2019), Some(28));
}
}

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,879 @@
use super::{Days, MAX_YEAR, MIN_YEAR, Months, NaiveDate};
use crate::naive::internals::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags};
use crate::{Datelike, TimeDelta, Weekday};
// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`,
// we use a separate run-time test.
#[test]
fn test_date_bounds() {
let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap();
let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap();
assert!(
NaiveDate::MIN == calculated_min,
"`NaiveDate::MIN` should have year flag {:?}",
calculated_min.year_flags()
);
assert!(
NaiveDate::MAX == calculated_max,
"`NaiveDate::MAX` should have year flag {:?} and ordinal {}",
calculated_max.year_flags(),
calculated_max.ordinal()
);
// let's also check that the entire range do not exceed 2^44 seconds
// (sometimes used for bounding `TimeDelta` against overflow)
let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds();
let maxsecs = maxsecs + 86401; // also take care of DateTime
assert!(
maxsecs < (1 << MAX_BITS),
"The entire `NaiveDate` range somehow exceeds 2^{} seconds",
MAX_BITS
);
const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN;
assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year()));
assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31));
const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX;
assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year()));
assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1));
}
#[test]
fn diff_months() {
// identity
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)),
Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap())
);
// add with months exceeding `i32::MAX`
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3)
.unwrap()
.checked_add_months(Months::new(i32::MAX as u32 + 1)),
None
);
// sub with months exceeding `i32::MIN`
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3)
.unwrap()
.checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)),
None
);
// add overflowing year
assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None);
// add underflowing year
assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None);
// sub crossing year 0 boundary
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)),
Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap())
);
// add crossing year boundary
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)),
Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap())
);
// sub crossing year boundary
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)),
Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap())
);
// add clamping day, non-leap year
assert_eq!(
NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)),
Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap())
);
// add to leap day
assert_eq!(
NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)),
Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap())
);
// add into december
assert_eq!(
NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)),
Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap())
);
// sub into december
assert_eq!(
NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)),
Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap())
);
// add into january
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)),
Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap())
);
// sub into january
assert_eq!(
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)),
Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap())
);
}
#[test]
fn test_readme_doomsday() {
for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
// even months
let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap();
let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap();
let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap();
let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap();
let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap();
// nine to five, seven-eleven
let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap();
let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap();
let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap();
let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap();
// "March 0"
let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap();
let weekday = d30.weekday();
let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117];
assert!(other_dates.iter().all(|d| d.weekday() == weekday));
}
}
#[test]
fn test_date_from_ymd() {
let from_ymd = NaiveDate::from_ymd_opt;
assert!(from_ymd(2012, 0, 1).is_none());
assert!(from_ymd(2012, 1, 1).is_some());
assert!(from_ymd(2012, 2, 29).is_some());
assert!(from_ymd(2014, 2, 29).is_none());
assert!(from_ymd(2014, 3, 0).is_none());
assert!(from_ymd(2014, 3, 1).is_some());
assert!(from_ymd(2014, 3, 31).is_some());
assert!(from_ymd(2014, 3, 32).is_none());
assert!(from_ymd(2014, 12, 31).is_some());
assert!(from_ymd(2014, 13, 1).is_none());
}
#[test]
fn test_date_from_yo() {
let from_yo = NaiveDate::from_yo_opt;
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(from_yo(2012, 0), None);
assert_eq!(from_yo(2012, 1), Some(ymd(2012, 1, 1)));
assert_eq!(from_yo(2012, 2), Some(ymd(2012, 1, 2)));
assert_eq!(from_yo(2012, 32), Some(ymd(2012, 2, 1)));
assert_eq!(from_yo(2012, 60), Some(ymd(2012, 2, 29)));
assert_eq!(from_yo(2012, 61), Some(ymd(2012, 3, 1)));
assert_eq!(from_yo(2012, 100), Some(ymd(2012, 4, 9)));
assert_eq!(from_yo(2012, 200), Some(ymd(2012, 7, 18)));
assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26)));
assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31)));
assert_eq!(from_yo(2012, 367), None);
assert_eq!(from_yo(2012, (1 << 28) | 60), None);
assert_eq!(from_yo(2014, 0), None);
assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1)));
assert_eq!(from_yo(2014, 2), Some(ymd(2014, 1, 2)));
assert_eq!(from_yo(2014, 32), Some(ymd(2014, 2, 1)));
assert_eq!(from_yo(2014, 59), Some(ymd(2014, 2, 28)));
assert_eq!(from_yo(2014, 60), Some(ymd(2014, 3, 1)));
assert_eq!(from_yo(2014, 100), Some(ymd(2014, 4, 10)));
assert_eq!(from_yo(2014, 200), Some(ymd(2014, 7, 19)));
assert_eq!(from_yo(2014, 300), Some(ymd(2014, 10, 27)));
assert_eq!(from_yo(2014, 365), Some(ymd(2014, 12, 31)));
assert_eq!(from_yo(2014, 366), None);
}
#[test]
fn test_date_from_isoywd() {
let from_isoywd = NaiveDate::from_isoywd_opt;
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(from_isoywd(2004, 0, Weekday::Sun), None);
assert_eq!(from_isoywd(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29)));
assert_eq!(from_isoywd(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4)));
assert_eq!(from_isoywd(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5)));
assert_eq!(from_isoywd(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11)));
assert_eq!(from_isoywd(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20)));
assert_eq!(from_isoywd(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26)));
assert_eq!(from_isoywd(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27)));
assert_eq!(from_isoywd(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2)));
assert_eq!(from_isoywd(2004, 54, Weekday::Mon), None);
assert_eq!(from_isoywd(2011, 0, Weekday::Sun), None);
assert_eq!(from_isoywd(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3)));
assert_eq!(from_isoywd(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9)));
assert_eq!(from_isoywd(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10)));
assert_eq!(from_isoywd(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16)));
assert_eq!(from_isoywd(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17)));
assert_eq!(from_isoywd(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23)));
assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24)));
assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30)));
assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None);
}
#[test]
fn test_date_from_isoywd_and_iso_week() {
for year in 2000..2401 {
for week in 1..54 {
for &weekday in [
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
]
.iter()
{
let d = NaiveDate::from_isoywd_opt(year, week, weekday);
if let Some(d) = d {
assert_eq!(d.weekday(), weekday);
let w = d.iso_week();
assert_eq!(w.year(), year);
assert_eq!(w.week(), week);
}
}
}
}
for year in 2000..2401 {
for month in 1..13 {
for day in 1..32 {
let d = NaiveDate::from_ymd_opt(year, month, day);
if let Some(d) = d {
let w = d.iso_week();
let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday());
assert_eq!(d, d_.unwrap());
}
}
}
}
}
#[test]
fn test_date_from_num_days_from_ce() {
let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt;
assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap()));
assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap()));
assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap()));
assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap()));
assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap()));
assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap()));
assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap()));
assert_eq!(
from_ndays_from_ce(146097 * 5 + 1),
Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap())
);
assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE
assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()));
assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE
for days in (-9999..10001).map(|x| x * 100) {
assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days));
}
assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN));
assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None);
assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX));
assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None);
assert_eq!(from_ndays_from_ce(i32::MIN), None);
assert_eq!(from_ndays_from_ce(i32::MAX), None);
}
#[test]
fn test_date_from_weekday_of_month_opt() {
let ymwd = NaiveDate::from_weekday_of_month_opt;
assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None);
assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap()));
assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None);
}
#[test]
fn test_date_fields() {
fn check(year: i32, month: u32, day: u32, ordinal: u32) {
let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap();
assert_eq!(d1.year(), year);
assert_eq!(d1.month(), month);
assert_eq!(d1.day(), day);
assert_eq!(d1.ordinal(), ordinal);
let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap();
assert_eq!(d2.year(), year);
assert_eq!(d2.month(), month);
assert_eq!(d2.day(), day);
assert_eq!(d2.ordinal(), ordinal);
assert_eq!(d1, d2);
}
check(2012, 1, 1, 1);
check(2012, 1, 2, 2);
check(2012, 2, 1, 32);
check(2012, 2, 29, 60);
check(2012, 3, 1, 61);
check(2012, 4, 9, 100);
check(2012, 7, 18, 200);
check(2012, 10, 26, 300);
check(2012, 12, 31, 366);
check(2014, 1, 1, 1);
check(2014, 1, 2, 2);
check(2014, 2, 1, 32);
check(2014, 2, 28, 59);
check(2014, 3, 1, 60);
check(2014, 4, 10, 100);
check(2014, 7, 19, 200);
check(2014, 10, 27, 300);
check(2014, 12, 31, 365);
}
#[test]
fn test_date_weekday() {
assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri);
// May 20, 1875 = ISO 8601 reference date
assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu);
assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat);
}
#[test]
fn test_date_with_fields() {
let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap();
assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap()));
assert_eq!(d.with_year(-100), None);
assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap()));
assert_eq!(d.with_year(1900), None);
assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
assert_eq!(d.with_year(2001), None);
assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap()));
assert_eq!(d.with_year(i32::MAX), None);
let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap();
assert_eq!(d.with_month(0), None);
assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap()));
assert_eq!(d.with_month(2), None);
assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap()));
assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap()));
assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap()));
assert_eq!(d.with_month(13), None);
assert_eq!(d.with_month(u32::MAX), None);
let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap();
assert_eq!(d.with_day(0), None);
assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap()));
assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
assert_eq!(d.with_day(30), None);
assert_eq!(d.with_day(u32::MAX), None);
}
#[test]
fn test_date_with_ordinal() {
let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap();
assert_eq!(d.with_ordinal(0), None);
assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()));
assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap()));
assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap()));
assert_eq!(d.with_ordinal(367), None);
assert_eq!(d.with_ordinal((1 << 28) | 60), None);
let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap();
assert_eq!(d.with_ordinal(366), None);
assert_eq!(d.with_ordinal(u32::MAX), None);
}
#[test]
fn test_date_num_days_from_ce() {
assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1);
for year in -9999..10001 {
assert_eq!(
NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(),
NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1
);
}
}
#[test]
fn test_date_succ() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7)));
assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1)));
assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1)));
assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29)));
assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None);
}
#[test]
fn test_date_pred() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29)));
assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31)));
assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31)));
assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6)));
assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None);
}
#[test]
fn test_date_checked_add_signed() {
fn check(lhs: Option<NaiveDate>, delta: TimeDelta, rhs: Option<NaiveDate>) {
assert_eq!(lhs.unwrap().checked_add_signed(delta), rhs);
assert_eq!(lhs.unwrap().checked_sub_signed(-delta), rhs);
}
let ymd = NaiveDate::from_ymd_opt;
check(ymd(2014, 1, 1), TimeDelta::zero(), ymd(2014, 1, 1));
check(ymd(2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), ymd(2014, 1, 1));
// always round towards zero
check(ymd(2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), ymd(2014, 1, 1));
check(ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap(), ymd(2014, 1, 2));
check(ymd(2014, 1, 1), TimeDelta::try_days(-1).unwrap(), ymd(2013, 12, 31));
check(ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap(), ymd(2014, 12, 31));
check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), ymd(2018, 1, 1));
check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), ymd(2414, 1, 1));
check(ymd(-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), ymd(5, 1, 1));
// overflow check
check(
ymd(0, 1, 1),
TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(),
ymd(MAX_YEAR, 12, 31),
);
check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None);
check(ymd(0, 1, 1), TimeDelta::MAX, None);
check(
ymd(0, 1, 1),
TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(),
ymd(MIN_YEAR, 1, 1),
);
check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None);
check(ymd(0, 1, 1), TimeDelta::MIN, None);
}
#[test]
fn test_date_signed_duration_since() {
fn check(lhs: Option<NaiveDate>, rhs: Option<NaiveDate>, delta: TimeDelta) {
assert_eq!(lhs.unwrap().signed_duration_since(rhs.unwrap()), delta);
assert_eq!(rhs.unwrap().signed_duration_since(lhs.unwrap()), -delta);
}
let ymd = NaiveDate::from_ymd_opt;
check(ymd(2014, 1, 1), ymd(2014, 1, 1), TimeDelta::zero());
check(ymd(2014, 1, 2), ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap());
check(ymd(2014, 12, 31), ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap());
check(ymd(2015, 1, 3), ymd(2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap());
check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap());
check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap());
check(
ymd(MAX_YEAR, 12, 31),
ymd(0, 1, 1),
TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(),
);
check(
ymd(MIN_YEAR, 1, 1),
ymd(0, 1, 1),
TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(),
);
}
#[test]
fn test_date_add_days() {
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
assert_eq!(lhs.unwrap().checked_add_days(days), rhs);
}
let ymd = NaiveDate::from_ymd_opt;
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
// always round towards zero
check(ymd(2014, 1, 1), Days::new(1), ymd(2014, 1, 2));
check(ymd(2014, 1, 1), Days::new(364), ymd(2014, 12, 31));
check(ymd(2014, 1, 1), Days::new(365 * 4 + 1), ymd(2018, 1, 1));
check(ymd(2014, 1, 1), Days::new(365 * 400 + 97), ymd(2414, 1, 1));
check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1));
// overflow check
check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31));
check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None);
}
#[test]
fn test_date_sub_days() {
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
assert_eq!(lhs.unwrap().checked_sub_days(days), rhs);
}
let ymd = NaiveDate::from_ymd_opt;
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1));
check(ymd(2014, 12, 31), Days::new(364), ymd(2014, 1, 1));
check(ymd(2015, 1, 3), Days::new(365 + 2), ymd(2014, 1, 1));
check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1));
check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1));
check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1));
check(
ymd(0, 1, 1),
Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()),
ymd(MIN_YEAR, 1, 1),
);
}
#[test]
fn test_date_addassignment() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
let mut date = ymd(2016, 10, 1);
date += TimeDelta::try_days(10).unwrap();
assert_eq!(date, ymd(2016, 10, 11));
date += TimeDelta::try_days(30).unwrap();
assert_eq!(date, ymd(2016, 11, 10));
}
#[test]
fn test_date_subassignment() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
let mut date = ymd(2016, 10, 11);
date -= TimeDelta::try_days(10).unwrap();
assert_eq!(date, ymd(2016, 10, 1));
date -= TimeDelta::try_days(2).unwrap();
assert_eq!(date, ymd(2016, 9, 29));
}
#[test]
fn test_date_fmt() {
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04");
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04");
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04");
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04");
assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04");
assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04");
assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04");
assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04");
// the format specifier should have no effect on `NaiveTime`
assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06");
assert_eq!(format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07");
}
#[test]
fn test_date_from_str() {
// valid cases
let valid = [
"-0000000123456-1-2",
" -123456 - 1 - 2 ",
"-12345-1-2",
"-1234-12-31",
"-7-6-5",
"350-2-28",
"360-02-29",
"0360-02-29",
"2015-2 -18",
"2015-02-18",
"+70-2-18",
"+70000-2-18",
"+00007-2-18",
];
for &s in &valid {
eprintln!("test_date_from_str valid {:?}", s);
let d = match s.parse::<NaiveDate>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
eprintln!("d {:?} (NaiveDate)", d);
let s_ = format!("{:?}", d);
eprintln!("s_ {:?}", s_);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveDate>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
eprintln!("d_ {:?} (NaiveDate)", d_);
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
);
}
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
let invalid = [
"", // empty
"x", // invalid
"Fri, 09 Aug 2013 GMT", // valid date, wrong format
"Sat Jun 30 2012", // valid date, wrong format
"1441497364.649", // valid datetime, wrong format
"+1441497364.649", // valid datetime, wrong format
"+1441497364", // valid datetime, wrong format
"2014/02/03", // valid date, wrong format
"2014", // datetime missing data
"2014-01", // datetime missing data
"2014-01-00", // invalid day
"2014-11-32", // invalid day
"2014-13-01", // invalid month
"2014-13-57", // invalid month, day
"9999999-9-9", // invalid year (out of bounds)
];
for &s in &invalid {
eprintln!("test_date_from_str invalid {:?}", s);
assert!(s.parse::<NaiveDate>().is_err());
}
}
#[test]
fn test_date_parse_from_str() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
assert_eq!(
NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(ymd(2014, 5, 7))
); // ignore time and offset
assert_eq!(
NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"),
Ok(ymd(2015, 2, 2))
);
assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9)));
assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err());
assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err());
assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient
assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter
assert_eq!(
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2020, 1, 12),
);
assert_eq!(
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2019, 1, 13),
);
}
#[test]
fn test_day_iterator_limit() {
assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2);
assert_eq!(
NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(),
2
);
}
#[test]
fn test_week_iterator_limit() {
assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2);
assert_eq!(
NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(),
2
);
}
#[test]
fn test_weeks_from() {
// tests per: https://github.com/chronotope/chrono/issues/961
// these internally use `weeks_from` via the parsing infrastructure
assert_eq!(
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2020, 1, 12),
);
assert_eq!(
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2019, 1, 13),
);
// direct tests
for (y, starts_on) in &[
(2019, Weekday::Tue),
(2020, Weekday::Wed),
(2021, Weekday::Fri),
(2022, Weekday::Sat),
(2023, Weekday::Sun),
(2024, Weekday::Mon),
(2025, Weekday::Wed),
(2026, Weekday::Thu),
] {
for day in &[
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
] {
assert_eq!(
NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)),
Some(if day == starts_on { 1 } else { 0 })
);
// last day must always be in week 52 or 53
assert!(
[52, 53].contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),
);
}
}
let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap();
// 400 years covers all year types
for day in &[
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
] {
// must always be below 54
for dplus in 1..(400 * 366) {
assert!((base + Days::new(dplus)).weeks_from(*day) < 54)
}
}
}
#[test]
fn test_with_0_overflow() {
let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap();
assert!(dt.with_month0(4294967295).is_none());
assert!(dt.with_day0(4294967295).is_none());
assert!(dt.with_ordinal0(4294967295).is_none());
}
#[test]
fn test_leap_year() {
for year in 0..=MAX_YEAR {
let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
assert_eq!(date.leap_year(), is_leap);
assert_eq!(date.leap_year(), date.with_ordinal(366).is_some());
}
}
#[test]
fn test_date_yearflags() {
for (year, year_flags, _) in YEAR_FLAGS {
assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags);
}
}
#[test]
fn test_weekday_with_yearflags() {
for (year, year_flags, first_weekday) in YEAR_FLAGS {
let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap();
dbg!(year);
assert_eq!(first_day_of_year.year_flags(), year_flags);
assert_eq!(first_day_of_year.weekday(), first_weekday);
let mut prev = first_day_of_year.weekday();
for ordinal in 2u32..=year_flags.ndays() {
let date = NaiveDate::from_yo_opt(year, ordinal).unwrap();
let expected = prev.succ();
assert_eq!(date.weekday(), expected);
prev = expected;
}
}
}
#[test]
fn test_isoweekdate_with_yearflags() {
for (year, year_flags, _) in YEAR_FLAGS {
// January 4 should be in the first week
let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap();
let iso_week = jan4.iso_week();
assert_eq!(jan4.year_flags(), year_flags);
assert_eq!(iso_week.week(), 1);
}
}
#[test]
fn test_date_to_mdf_to_date() {
for (year, year_flags, _) in YEAR_FLAGS {
for ordinal in 1..=year_flags.ndays() {
let date = NaiveDate::from_yo_opt(year, ordinal).unwrap();
assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap());
}
}
}
// Used for testing some methods with all combinations of `YearFlags`.
// (year, flags, first weekday of year)
const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [
(2006, A, Weekday::Sun),
(2005, B, Weekday::Sat),
(2010, C, Weekday::Fri),
(2009, D, Weekday::Thu),
(2003, E, Weekday::Wed),
(2002, F, Weekday::Tue),
(2001, G, Weekday::Mon),
(2012, AG, Weekday::Sun),
(2000, BA, Weekday::Sat),
(2016, CB, Weekday::Fri),
(2004, DC, Weekday::Thu),
(2020, ED, Weekday::Wed),
(2008, FE, Weekday::Tue),
(2024, GF, Weekday::Mon),
];
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let date_min = NaiveDate::MIN;
let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveDate>(&bytes).unwrap(), date_min);
let date_max = NaiveDate::MAX;
let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveDate>(&bytes).unwrap(), date_max);
}
// MAX_YEAR-12-31 minus 0000-01-01
// = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01)
// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365
// = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR)
const MAX_DAYS_FROM_YEAR_0: i32 =
(MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400;
// MIN_YEAR-01-01 minus 0000-01-01
// = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000)
const MIN_DAYS_FROM_YEAR_0: i32 = MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400;
// only used for testing, but duplicated in naive::datetime
const MAX_BITS: usize = 44;

File diff suppressed because it is too large Load Diff

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,408 @@
use super::NaiveDateTime;
use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc};
#[test]
fn test_datetime_add() {
fn check(
(y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32),
rhs: TimeDelta,
result: Option<(i32, u32, u32, u32, u32, u32)>,
) {
let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let sum = result.map(|(y, m, d, h, n, s)| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()
});
assert_eq!(lhs.checked_add_signed(rhs), sum);
assert_eq!(lhs.checked_sub_signed(-rhs), sum);
}
let seconds = |s| TimeDelta::try_seconds(s).unwrap();
check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10)));
check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8)));
check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8)));
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
// overflow check
// assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`.
// (they are private constants, but the equivalence is tested in that module.)
let max_days_from_year_0 =
NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap());
check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0)));
check(
(0, 1, 1, 0, 0, 0),
max_days_from_year_0 + seconds(86399),
Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)),
);
check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None);
check((0, 1, 1, 0, 0, 0), TimeDelta::MAX, None);
let min_days_from_year_0 =
NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap());
check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0)));
check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None);
check((0, 1, 1, 0, 0, 0), TimeDelta::MIN, None);
}
#[test]
fn test_datetime_sub() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let since = NaiveDateTime::signed_duration_since;
assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero());
assert_eq!(
since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)),
TimeDelta::try_seconds(1).unwrap()
);
assert_eq!(
since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
TimeDelta::try_seconds(-1).unwrap()
);
assert_eq!(
since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)),
TimeDelta::try_seconds(86399).unwrap()
);
assert_eq!(
since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)),
TimeDelta::try_seconds(999_999_999).unwrap()
);
}
#[test]
fn test_datetime_addassignment() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let mut date = ymdhms(2016, 10, 1, 10, 10, 10);
date += TimeDelta::try_minutes(10_000_000).unwrap();
assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10));
date += TimeDelta::try_days(10).unwrap();
assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10));
}
#[test]
fn test_datetime_subassignment() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let mut date = ymdhms(2016, 10, 1, 10, 10, 10);
date -= TimeDelta::try_minutes(10_000_000).unwrap();
assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10));
date -= TimeDelta::try_days(10).unwrap();
assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10));
}
#[test]
fn test_core_duration_ops() {
use core::time::Duration;
let mut dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap();
let same = dt + Duration::ZERO;
assert_eq!(dt, same);
dt += Duration::new(3600, 0);
assert_eq!(dt, NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(12, 34, 12).unwrap());
}
#[test]
#[should_panic]
fn test_core_duration_max() {
use core::time::Duration;
let mut utc_dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap();
utc_dt += Duration::MAX;
}
#[test]
fn test_datetime_from_str() {
// valid cases
let valid = [
"2001-02-03T04:05:06",
"2012-12-12T12:12:12",
"2015-02-18T23:16:09.153",
"2015-2-18T23:16:09.153",
"-77-02-18T23:16:09",
"+82701-05-6T15:9:60.898989898989",
" +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ",
];
for &s in &valid {
eprintln!("test_parse_naivedatetime valid {:?}", s);
let d = match s.parse::<NaiveDateTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
let s_ = format!("{:?}", d);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveDateTime>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
);
}
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
let invalid = [
"", // empty
"x", // invalid / missing data
"15", // missing data
"15:8:9", // looks like a time (invalid date)
"15-8-9", // looks like a date (invalid)
"Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format
"Sat Jun 30 23:59:60 2012", // valid date, wrong format
"1441497364.649", // valid date, wrong format
"+1441497364.649", // valid date, wrong format
"+1441497364", // valid date, wrong format
"2014/02/03 04:05:06", // valid date, wrong format
"2015-15-15T15:15:15", // invalid date
"2012-12-12T12:12:12x", // bad timezone / trailing literal
"2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal
"2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal
"2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal
"2012-123-12T12:12:12", // invalid month
"2012-12-12t12:12:12", // bad divider 't'
"2012-12-12 12:12:12", // missing divider 'T'
"2012-12-12T12:12:12Z", // trailing char 'Z'
"+ 82701-123-12T12:12:12", // strange year, invalid month
"+802701-123-12T12:12:12", // out-of-bound year, invalid month
];
for &s in &invalid {
eprintln!("test_datetime_from_str invalid {:?}", s);
assert!(s.parse::<NaiveDateTime>().is_err());
}
}
#[test]
fn test_datetime_parse_from_str() {
let ymdhms =
|y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
let ymdhmsn = |y, m, d, h, n, s, nano| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()
};
assert_eq!(
NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(ymdhms(2014, 5, 7, 12, 34, 56))
); // ignore offset
assert_eq!(
NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"),
Ok(ymdhms(2015, 2, 2, 0, 0, 0))
);
assert_eq!(
NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"),
Ok(ymdhms(2013, 8, 9, 23, 54, 35))
);
assert!(
NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT")
.is_err()
);
assert!(NaiveDateTime::parse_from_str("2014-5-7 Q2 12:3456", "%Y-%m-%d Q%q %H:%M:%S").is_err());
assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient
assert_eq!(
NaiveDateTime::parse_from_str("1441497364", "%s"),
Ok(ymdhms(2015, 9, 5, 23, 56, 4))
);
assert_eq!(
NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"),
Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234))
);
assert_eq!(
NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"),
Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"),
Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"),
Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645))
);
}
#[test]
fn test_datetime_parse_from_str_with_spaces() {
let parse_from_str = NaiveDateTime::parse_from_str;
let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap();
// with varying spaces - should succeed
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt));
// with varying spaces - should fail
// leading space in data
assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
// trailing space in data
assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err());
// trailing tab in data
assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err());
// mismatched newlines
assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
// trailing literal in data
assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err());
}
#[test]
fn test_datetime_add_sub_invariant() {
// issue #37
let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
let t = -946684799990000;
let time = base + TimeDelta::microseconds(t);
assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap());
}
#[test]
fn test_and_local_timezone() {
let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap();
let dt_utc = ndt.and_utc();
assert_eq!(dt_utc.naive_local(), ndt);
assert_eq!(dt_utc.timezone(), Utc);
let offset_tz = FixedOffset::west_opt(4 * 3600).unwrap();
let dt_offset = ndt.and_local_timezone(offset_tz).unwrap();
assert_eq!(dt_offset.naive_local(), ndt);
assert_eq!(dt_offset.timezone(), offset_tz);
}
#[test]
fn test_and_utc() {
let ndt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap();
let dt_utc = ndt.and_utc();
assert_eq!(dt_utc.naive_local(), ndt);
assert_eq!(dt_utc.timezone(), Utc);
}
#[test]
fn test_checked_add_offset() {
let ymdhmsm = |y, m, d, h, mn, s, mi| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi)
};
let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap();
assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap();
assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none());
let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap();
assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap();
assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none());
}
#[test]
fn test_checked_sub_offset() {
let ymdhmsm = |y, m, d, h, mn, s, mi| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi)
};
let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap();
assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap();
assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none());
let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap();
assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap();
assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none());
assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset));
assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset));
}
#[test]
fn test_overflowing_add_offset() {
let ymdhmsm = |y, m, d, h, mn, s, mi| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap()
};
let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0);
assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000);
assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX);
let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap();
// regular date
let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0);
assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0));
// leap second is preserved
let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000);
assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000));
// out of range
assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN);
}
#[test]
fn test_and_timezone_min_max_dates() {
for offset_hour in -23..=23 {
dbg!(offset_hour);
let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap();
let local_max = NaiveDateTime::MAX.and_local_timezone(offset);
if offset_hour >= 0 {
assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX);
} else {
assert_eq!(local_max, MappedLocalTime::None);
}
let local_min = NaiveDateTime::MIN.and_local_timezone(offset);
if offset_hour <= 0 {
assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN);
} else {
assert_eq!(local_min, MappedLocalTime::None);
}
}
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let dt_min = NaiveDateTime::MIN;
let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveDateTime>(&bytes).unwrap(), dt_min);
let dt_max = NaiveDateTime::MAX;
let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveDateTime>(&bytes).unwrap(), dt_max);
}

View File

@@ -1,59 +1,52 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
//! Internal helper types for working with dates.
//! The internal implementation of the calendar and ordinal date.
//!
//! The current implementation is optimized for determining year, month, day and day of week.
//! 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar,
//! which are included in every packed `NaiveDate` instance.
//! The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is
//! based on the moderately-sized lookup table (~1.5KB)
//! and the packed representation is chosen for the efficient lookup.
//! Every internal data structure does not validate its input,
//! but the conversion keeps the valid value valid and the invalid value invalid
//! so that the user-facing `NaiveDate` can validate the input as late as possible.
#![allow(dead_code)] // some internal methods have been left for consistency
#![cfg_attr(feature = "__internal_bench", allow(missing_docs))]
use core::{fmt, i32};
use div::{div_rem, mod_floor};
use num_traits::FromPrimitive;
use Weekday;
use core::fmt;
/// The internal date representation. This also includes the packed `Mdf` value.
pub type DateImpl = i32;
pub const MAX_YEAR: DateImpl = i32::MAX >> 13;
pub const MIN_YEAR: DateImpl = i32::MIN >> 13;
/// The year flags (aka the dominical letter).
/// Year flags (aka the dominical letter).
///
/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`.
///
/// There are 14 possible classes of year in the Gregorian calendar:
/// common and leap years starting with Monday through Sunday.
/// The `YearFlags` stores this information into 4 bits `abbb`,
/// where `a` is `1` for the common year (simplifies the `Of` validation)
/// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year
/// (simplifies the day of week calculation from the 1-based ordinal).
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct YearFlags(pub u8);
///
/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1`
/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero
/// `Weekday` of the last day in the preceding year.
#[allow(unreachable_pub)] // public as an alias for benchmarks only
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
pub struct YearFlags(pub(super) u8);
pub const A: YearFlags = YearFlags(0o15);
pub const AG: YearFlags = YearFlags(0o05);
pub const B: YearFlags = YearFlags(0o14);
pub const BA: YearFlags = YearFlags(0o04);
pub const C: YearFlags = YearFlags(0o13);
pub const CB: YearFlags = YearFlags(0o03);
pub const D: YearFlags = YearFlags(0o12);
pub const DC: YearFlags = YearFlags(0o02);
pub const E: YearFlags = YearFlags(0o11);
pub const ED: YearFlags = YearFlags(0o01);
pub const F: YearFlags = YearFlags(0o17);
pub const FE: YearFlags = YearFlags(0o07);
pub const G: YearFlags = YearFlags(0o16);
pub const GF: YearFlags = YearFlags(0o06);
// Weekday of the last day in the preceding year.
// Allows for quick day of week calculation from the 1-based ordinal.
const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`.
const YEAR_STARTS_AFTER_THUESDAY: u8 = 1;
const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2;
const YEAR_STARTS_AFTER_THURSDAY: u8 = 3;
const YEAR_STARTS_AFTER_FRIDAY: u8 = 4;
const YEAR_STARTS_AFTER_SATURDAY: u8 = 5;
const YEAR_STARTS_AFTER_SUNDAY: u8 = 6;
static YEAR_TO_FLAGS: [YearFlags; 400] = [
const COMMON_YEAR: u8 = 1 << 3;
const LEAP_YEAR: u8 = 0 << 3;
pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY);
pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY);
pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY);
pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY);
pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY);
pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY);
pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY);
pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY);
pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY);
pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY);
pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY);
pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY);
const YEAR_TO_FLAGS: &[YearFlags; 400] = &[
BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA,
G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G,
F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F,
@@ -72,66 +65,31 @@ static YEAR_TO_FLAGS: [YearFlags; 400] = [
D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400
];
static YEAR_DELTAS: [u8; 401] = [
0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14,
15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20,
21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100
25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30,
30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36,
36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42,
42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48,
48, 49, 49, 49, // 200
49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54,
54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60,
60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66,
66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72,
72, 73, 73, 73, // 300
73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78,
78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84,
84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90,
90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96,
96, 97, 97, 97, 97, // 400+1
];
pub fn cycle_to_yo(cycle: u32) -> (u32, u32) {
let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365);
let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]);
if ordinal0 < delta {
year_mod_400 -= 1;
ordinal0 += 365 - u32::from(YEAR_DELTAS[year_mod_400 as usize]);
} else {
ordinal0 -= delta;
}
(year_mod_400, ordinal0 + 1)
}
pub fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 {
year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1
}
impl YearFlags {
#[allow(unreachable_pub)] // public as an alias for benchmarks only
#[doc(hidden)] // for benchmarks only
#[inline]
pub fn from_year(year: i32) -> YearFlags {
let year = mod_floor(year, 400);
#[must_use]
pub const fn from_year(year: i32) -> YearFlags {
let year = year.rem_euclid(400);
YearFlags::from_year_mod_400(year)
}
#[inline]
pub fn from_year_mod_400(year: i32) -> YearFlags {
pub(super) const fn from_year_mod_400(year: i32) -> YearFlags {
YEAR_TO_FLAGS[year as usize]
}
#[inline]
pub fn ndays(&self) -> u32 {
pub(super) const fn ndays(&self) -> u32 {
let YearFlags(flags) = *self;
366 - u32::from(flags >> 3)
366 - (flags >> 3) as u32
}
#[inline]
pub fn isoweek_delta(&self) -> u32 {
pub(super) const fn isoweek_delta(&self) -> u32 {
let YearFlags(flags) = *self;
let mut delta = u32::from(flags) & 0b0111;
let mut delta = (flags & 0b0111) as u32;
if delta < 3 {
delta += 7;
}
@@ -139,7 +97,7 @@ impl YearFlags {
}
#[inline]
pub fn nisoweeks(&self) -> u32 {
pub(super) const fn nisoweeks(&self) -> u32 {
let YearFlags(flags) = *self;
52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1)
}
@@ -170,13 +128,15 @@ impl fmt::Debug for YearFlags {
}
}
pub const MIN_OL: u32 = 1 << 1;
pub const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1`
pub const MIN_MDL: u32 = (1 << 6) | (1 << 1);
pub const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
// OL: (ordinal << 1) | leap year flag
const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year
const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
const XX: i8 = -128;
static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [
// The next table are adjustment values to convert a date encoded as month-day-leapyear to
// ordinal-leapyear. OL = MDL - adjustment.
// Dates that do not exist are encoded as `XX`.
const XX: i8 = 0;
const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0
@@ -219,7 +179,7 @@ static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [
100, // 12
];
static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [
const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[
0, 0, // 0
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
@@ -260,209 +220,147 @@ static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [
98, // 12
];
/// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`.
///
/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag),
/// which is an index to the `OL_TO_MDL` lookup table.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub struct Of(pub u32);
impl Of {
#[inline]
fn clamp_ordinal(ordinal: u32) -> u32 {
if ordinal > 366 {
0
} else {
ordinal
}
}
#[inline]
pub fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of {
let ordinal = Of::clamp_ordinal(ordinal);
Of((ordinal << 4) | u32::from(flags))
}
#[inline]
pub fn from_mdf(Mdf(mdf): Mdf) -> Of {
let mdl = mdf >> 3;
match MDL_TO_OL.get(mdl as usize) {
Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)),
None => Of(0),
}
}
#[inline]
pub fn valid(&self) -> bool {
let Of(of) = *self;
let ol = of >> 3;
MIN_OL <= ol && ol <= MAX_OL
}
#[inline]
pub fn ordinal(&self) -> u32 {
let Of(of) = *self;
of >> 4
}
#[inline]
pub fn with_ordinal(&self, ordinal: u32) -> Of {
let ordinal = Of::clamp_ordinal(ordinal);
let Of(of) = *self;
Of((of & 0b1111) | (ordinal << 4))
}
#[inline]
pub fn flags(&self) -> YearFlags {
let Of(of) = *self;
YearFlags((of & 0b1111) as u8)
}
#[inline]
pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of {
let Of(of) = *self;
Of((of & !0b1111) | u32::from(flags))
}
#[inline]
pub fn weekday(&self) -> Weekday {
let Of(of) = *self;
Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap()
}
#[inline]
pub fn isoweekdate_raw(&self) -> (u32, Weekday) {
// week ordinal = ordinal + delta
let Of(of) = *self;
let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta());
(weekord / 7, Weekday::from_u32(weekord % 7).unwrap())
}
#[inline]
pub fn to_mdf(&self) -> Mdf {
Mdf::from_of(*self)
}
#[inline]
pub fn succ(&self) -> Of {
let Of(of) = *self;
Of(of + (1 << 4))
}
#[inline]
pub fn pred(&self) -> Of {
let Of(of) = *self;
Of(of - (1 << 4))
}
}
impl fmt::Debug for Of {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Of(of) = *self;
write!(
f,
"Of(({} << 4) | {:#04o} /*{:?}*/)",
of >> 4,
of & 0b1111,
YearFlags((of & 0b1111) as u8)
)
}
}
/// Month, day of month and year flags: `(month << 9) | (day << 4) | flags`
/// `M_MMMD_DDDD_LFFF`
///
/// The whole bits except for the least 3 bits are referred as `Mdl`
/// (month, day of month and leap flag),
/// which is an index to the `MDL_TO_OL` lookup table.
/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap
/// year flag), which is an index to the `MDL_TO_OL` lookup table.
///
/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is
/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for
/// efficient lookup.
///
/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like
/// February 30, can still be represented. This allows the validation to be combined with the final
/// table lookup, which is good for performance.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub struct Mdf(pub u32);
pub(super) struct Mdf(u32);
impl Mdf {
/// Makes a new `Mdf` value from month, day and `YearFlags`.
///
/// This method doesn't fully validate the range of the `month` and `day` parameters, only as
/// much as what can't be deferred until later. The year `flags` are trusted to be correct.
///
/// # Errors
///
/// Returns `None` if `month > 12` or `day > 31`.
#[inline]
fn clamp_month(month: u32) -> u32 {
if month > 12 {
0
} else {
month
pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
match month <= 12 && day <= 31 {
true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
false => None,
}
}
/// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year
/// `flags`.
///
/// The `ol` is trusted to be valid, and the `flags` are trusted to match it.
#[inline]
fn clamp_day(day: u32) -> u32 {
if day > 31 {
0
} else {
day
}
pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf {
debug_assert!(ol > 1 && ol <= MAX_OL as i32);
// Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value.
Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32)
}
/// Returns the month of this `Mdf`.
#[inline]
pub fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf {
let month = Mdf::clamp_month(month);
let day = Mdf::clamp_day(day);
Mdf((month << 9) | (day << 4) | u32::from(flags))
}
#[inline]
pub fn from_of(Of(of): Of) -> Mdf {
let ol = of >> 3;
match OL_TO_MDL.get(ol as usize) {
Some(&v) => Mdf(of + (u32::from(v) << 3)),
None => Mdf(0),
}
}
#[inline]
pub fn valid(&self) -> bool {
let Mdf(mdf) = *self;
let mdl = mdf >> 3;
match MDL_TO_OL.get(mdl as usize) {
Some(&v) => v >= 0,
None => false,
}
}
#[inline]
pub fn month(&self) -> u32 {
pub(super) const fn month(&self) -> u32 {
let Mdf(mdf) = *self;
mdf >> 9
}
/// Replaces the month of this `Mdf`, keeping the day and flags.
///
/// # Errors
///
/// Returns `None` if `month > 12`.
#[inline]
pub fn with_month(&self, month: u32) -> Mdf {
let month = Mdf::clamp_month(month);
pub(super) const fn with_month(&self, month: u32) -> Option<Mdf> {
if month > 12 {
return None;
}
let Mdf(mdf) = *self;
Mdf((mdf & 0b1_1111_1111) | (month << 9))
Some(Mdf((mdf & 0b1_1111_1111) | (month << 9)))
}
/// Returns the day of this `Mdf`.
#[inline]
pub fn day(&self) -> u32 {
pub(super) const fn day(&self) -> u32 {
let Mdf(mdf) = *self;
(mdf >> 4) & 0b1_1111
}
/// Replaces the day of this `Mdf`, keeping the month and flags.
///
/// # Errors
///
/// Returns `None` if `day > 31`.
#[inline]
pub fn with_day(&self, day: u32) -> Mdf {
let day = Mdf::clamp_day(day);
pub(super) const fn with_day(&self, day: u32) -> Option<Mdf> {
if day > 31 {
return None;
}
let Mdf(mdf) = *self;
Mdf((mdf & !0b1_1111_0000) | (day << 4))
Some(Mdf((mdf & !0b1_1111_0000) | (day << 4)))
}
/// Replaces the flags of this `Mdf`, keeping the month and day.
#[inline]
pub fn flags(&self) -> YearFlags {
pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
let Mdf(mdf) = *self;
YearFlags((mdf & 0b1111) as u8)
Mdf((mdf & !0b1111) | flags as u32)
}
/// Returns the ordinal that corresponds to this `Mdf`.
///
/// This does a table lookup to calculate the corresponding ordinal. It will return an error if
/// the `Mdl` turns out not to be a valid date.
///
/// # Errors
///
/// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
/// given month.
#[inline]
pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
let Mdf(mdf) = *self;
Mdf((mdf & !0b1111) | u32::from(flags))
pub(super) const fn ordinal(&self) -> Option<u32> {
let mdl = self.0 >> 3;
match MDL_TO_OL[mdl as usize] {
XX => None,
v => Some((mdl - v as u8 as u32) >> 1),
}
}
/// Returns the year flags of this `Mdf`.
#[inline]
pub fn to_of(&self) -> Of {
Of::from_mdf(*self)
pub(super) const fn year_flags(&self) -> YearFlags {
YearFlags((self.0 & 0b1111) as u8)
}
/// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags.
///
/// This does a table lookup to calculate the corresponding ordinal. It will return an error if
/// the `Mdl` turns out not to be a valid date.
///
/// # Errors
///
/// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
/// given month.
#[inline]
pub(super) const fn ordinal_and_flags(&self) -> Option<i32> {
let mdl = self.0 >> 3;
match MDL_TO_OL[mdl as usize] {
XX => None,
v => Some(self.0 as i32 - ((v as i32) << 3)),
}
}
#[cfg(test)]
fn valid(&self) -> bool {
let mdl = self.0 >> 3;
MDL_TO_OL[mdl as usize] > 0
}
}
@@ -482,14 +380,8 @@ impl fmt::Debug for Mdf {
#[cfg(test)]
mod tests {
#[cfg(test)]
extern crate num_iter;
use self::num_iter::range_inclusive;
use super::{Mdf, Of};
use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF};
use std::u32;
use Weekday;
use super::Mdf;
use super::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags};
const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G];
const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF];
@@ -530,43 +422,17 @@ mod tests {
assert_eq!(GF.nisoweeks(), 52);
}
#[test]
fn test_of() {
fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) {
for ordinal in range_inclusive(ordinal1, ordinal2) {
let of = Of::new(ordinal, flags);
assert!(
of.valid() == expected,
"ordinal {} = {:?} should be {} for dominical year {:?}",
ordinal,
of,
if expected { "valid" } else { "invalid" },
flags
);
}
}
for &flags in NONLEAP_FLAGS.iter() {
check(false, flags, 0, 0);
check(true, flags, 1, 365);
check(false, flags, 366, 1024);
check(false, flags, u32::MAX, u32::MAX);
}
for &flags in LEAP_FLAGS.iter() {
check(false, flags, 0, 0);
check(true, flags, 1, 366);
check(false, flags, 367, 1024);
check(false, flags, u32::MAX, u32::MAX);
}
}
#[test]
fn test_mdf_valid() {
fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
for month in range_inclusive(month1, month2) {
for day in range_inclusive(day1, day2) {
let mdf = Mdf::new(month, day, flags);
for month in month1..=month2 {
for day in day1..=day2 {
let mdf = match Mdf::new(month, day, flags) {
Some(mdf) => mdf,
None if !expected => continue,
None => panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags),
};
assert!(
mdf.valid() == expected,
"month {} day {} = {:?} should be {} for dominical year {:?}",
@@ -647,76 +513,16 @@ mod tests {
}
}
#[test]
fn test_of_fields() {
for &flags in FLAGS.iter() {
for ordinal in range_inclusive(1u32, 366) {
let of = Of::new(ordinal, flags);
if of.valid() {
assert_eq!(of.ordinal(), ordinal);
}
}
}
}
#[test]
fn test_of_with_fields() {
fn check(flags: YearFlags, ordinal: u32) {
let of = Of::new(ordinal, flags);
for ordinal in range_inclusive(0u32, 1024) {
let of = of.with_ordinal(ordinal);
assert_eq!(of.valid(), Of::new(ordinal, flags).valid());
if of.valid() {
assert_eq!(of.ordinal(), ordinal);
}
}
}
for &flags in NONLEAP_FLAGS.iter() {
check(flags, 1);
check(flags, 365);
}
for &flags in LEAP_FLAGS.iter() {
check(flags, 1);
check(flags, 366);
}
}
#[test]
fn test_of_weekday() {
assert_eq!(Of::new(1, A).weekday(), Weekday::Sun);
assert_eq!(Of::new(1, B).weekday(), Weekday::Sat);
assert_eq!(Of::new(1, C).weekday(), Weekday::Fri);
assert_eq!(Of::new(1, D).weekday(), Weekday::Thu);
assert_eq!(Of::new(1, E).weekday(), Weekday::Wed);
assert_eq!(Of::new(1, F).weekday(), Weekday::Tue);
assert_eq!(Of::new(1, G).weekday(), Weekday::Mon);
assert_eq!(Of::new(1, AG).weekday(), Weekday::Sun);
assert_eq!(Of::new(1, BA).weekday(), Weekday::Sat);
assert_eq!(Of::new(1, CB).weekday(), Weekday::Fri);
assert_eq!(Of::new(1, DC).weekday(), Weekday::Thu);
assert_eq!(Of::new(1, ED).weekday(), Weekday::Wed);
assert_eq!(Of::new(1, FE).weekday(), Weekday::Tue);
assert_eq!(Of::new(1, GF).weekday(), Weekday::Mon);
for &flags in FLAGS.iter() {
let mut prev = Of::new(1, flags).weekday();
for ordinal in range_inclusive(2u32, flags.ndays()) {
let of = Of::new(ordinal, flags);
let expected = prev.succ();
assert_eq!(of.weekday(), expected);
prev = expected;
}
}
}
#[test]
fn test_mdf_fields() {
for &flags in FLAGS.iter() {
for month in range_inclusive(1u32, 12) {
for day in range_inclusive(1u32, 31) {
let mdf = Mdf::new(month, day, flags);
for month in 1u32..=12 {
for day in 1u32..31 {
let mdf = match Mdf::new(month, day, flags) {
Some(mdf) => mdf,
None => continue,
};
if mdf.valid() {
assert_eq!(mdf.month(), month);
assert_eq!(mdf.day(), day);
@@ -729,20 +535,28 @@ mod tests {
#[test]
fn test_mdf_with_fields() {
fn check(flags: YearFlags, month: u32, day: u32) {
let mdf = Mdf::new(month, day, flags);
let mdf = Mdf::new(month, day, flags).unwrap();
for month in 0u32..=16 {
let mdf = match mdf.with_month(month) {
Some(mdf) => mdf,
None if month > 12 => continue,
None => panic!("failed to create Mdf with month {}", month),
};
for month in range_inclusive(0u32, 16) {
let mdf = mdf.with_month(month);
assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid());
if mdf.valid() {
assert_eq!(mdf.month(), month);
assert_eq!(mdf.day(), day);
}
}
for day in range_inclusive(0u32, 1024) {
let mdf = mdf.with_day(day);
assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid());
for day in 0u32..=1024 {
let mdf = match mdf.with_day(day) {
Some(mdf) => mdf,
None if day > 31 => continue,
None => panic!("failed to create Mdf with month {}", month),
};
if mdf.valid() {
assert_eq!(mdf.month(), month);
assert_eq!(mdf.day(), day);
@@ -769,47 +583,9 @@ mod tests {
}
#[test]
fn test_of_isoweekdate_raw() {
for &flags in FLAGS.iter() {
// January 4 should be in the first week
let (week, _) = Of::new(4 /* January 4 */, flags).isoweekdate_raw();
assert_eq!(week, 1);
}
}
#[test]
fn test_of_to_mdf() {
for i in range_inclusive(0u32, 8192) {
let of = Of(i);
assert_eq!(of.valid(), of.to_mdf().valid());
}
}
#[test]
fn test_mdf_to_of() {
for i in range_inclusive(0u32, 8192) {
let mdf = Mdf(i);
assert_eq!(mdf.valid(), mdf.to_of().valid());
}
}
#[test]
fn test_of_to_mdf_to_of() {
for i in range_inclusive(0u32, 8192) {
let of = Of(i);
if of.valid() {
assert_eq!(of, of.to_mdf().to_of());
}
}
}
#[test]
fn test_mdf_to_of_to_mdf() {
for i in range_inclusive(0u32, 8192) {
let mdf = Mdf(i);
if mdf.valid() {
assert_eq!(mdf, mdf.to_of().to_mdf());
}
}
fn test_mdf_new_range() {
let flags = YearFlags::from_year(2023);
assert!(Mdf::new(13, 1, flags).is_none());
assert!(Mdf::new(1, 32, flags).is_none());
}
}

View File

@@ -5,69 +5,80 @@
use core::fmt;
use super::internals::{DateImpl, Of, YearFlags};
use super::internals::YearFlags;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
/// ISO 8601 week.
///
/// This type, combined with [`Weekday`](../enum.Weekday.html),
/// constitues the ISO 8601 [week date](./struct.NaiveDate.html#week-date).
/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date).
/// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types
/// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq, PartialOrd)),
archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
pub struct IsoWeek {
// note that this allows for larger year range than `NaiveDate`.
// this is crucial because we have an edge case for the first and last week supported,
// Note that this allows for larger year range than `NaiveDate`.
// This is crucial because we have an edge case for the first and last week supported,
// which year number might not match the calendar year number.
ywf: DateImpl, // (year << 10) | (week << 4) | flag
}
/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value.
//
// internal use only. we don't expose the public constructor for `IsoWeek` for now,
// because the year range for the week date and the calendar date do not match and
// it is confusing to have a date that is out of range in one and not in another.
// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`.
pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek {
let (rawweek, _) = of.isoweekdate_raw();
let (year, week) = if rawweek < 1 {
// previous year
let prevlastweek = YearFlags::from_year(year - 1).nisoweeks();
(year - 1, prevlastweek)
} else {
let lastweek = of.flags().nisoweeks();
if rawweek > lastweek {
// next year
(year + 1, 1)
} else {
(year, rawweek)
}
};
IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) }
ywf: i32, // (year << 10) | (week << 4) | flag
}
impl IsoWeek {
/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value.
//
// Internal use only. We don't expose the public constructor for `IsoWeek` for now
// because the year range for the week date and the calendar date do not match, and
// it is confusing to have a date that is out of range in one and not in another.
// Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`.
pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self {
let rawweek = (ordinal + year_flags.isoweek_delta()) / 7;
let (year, week) = if rawweek < 1 {
// previous year
let prevlastweek = YearFlags::from_year(year - 1).nisoweeks();
(year - 1, prevlastweek)
} else {
let lastweek = year_flags.nisoweeks();
if rawweek > lastweek {
// next year
(year + 1, 1)
} else {
(year, rawweek)
}
};
let flags = YearFlags::from_year(year);
IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) }
}
/// Returns the year number for this ISO week.
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
/// ```
/// use chrono::{Datelike, NaiveDate, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap();
/// assert_eq!(d.iso_week().year(), 2015);
/// ~~~~
/// ```
///
/// This year number might not match the calendar year number.
/// Continuing the example...
///
/// ~~~~
/// ```
/// # use chrono::{NaiveDate, Datelike, Weekday};
/// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap();
/// assert_eq!(d.year(), 2014);
/// assert_eq!(d, NaiveDate::from_ymd(2014, 12, 29));
/// ~~~~
/// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap());
/// ```
#[inline]
pub fn year(&self) -> i32 {
pub const fn year(&self) -> i32 {
self.ywf >> 10
}
@@ -77,14 +88,14 @@ impl IsoWeek {
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
/// ```
/// use chrono::{Datelike, NaiveDate, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon);
/// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap();
/// assert_eq!(d.iso_week().week(), 15);
/// ~~~~
/// ```
#[inline]
pub fn week(&self) -> u32 {
pub const fn week(&self) -> u32 {
((self.ywf >> 4) & 0x3f) as u32
}
@@ -94,14 +105,14 @@ impl IsoWeek {
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
/// ```
/// use chrono::{Datelike, NaiveDate, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon);
/// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap();
/// assert_eq!(d.iso_week().week0(), 14);
/// ~~~~
/// ```
#[inline]
pub fn week0(&self) -> u32 {
pub const fn week0(&self) -> u32 {
((self.ywf >> 4) & 0x3f) as u32 - 1
}
}
@@ -112,26 +123,35 @@ impl IsoWeek {
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike};
/// ```
/// use chrono::{Datelike, NaiveDate};
///
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5).iso_week()), "2015-W36");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 3).iso_week()), "0000-W01");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31).iso_week()), "9999-W52");
/// ~~~~
/// assert_eq!(
/// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()),
/// "2015-W36"
/// );
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01");
/// assert_eq!(
/// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()),
/// "9999-W52"
/// );
/// ```
///
/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE.
///
/// ~~~~
/// ```
/// # use chrono::{NaiveDate, Datelike};
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 2).iso_week()), "-0001-W52");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31).iso_week()), "+10000-W52");
/// ~~~~
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52");
/// assert_eq!(
/// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()),
/// "+10000-W52"
/// );
/// ```
impl fmt::Debug for IsoWeek {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let year = self.year();
let week = self.week();
if 0 <= year && year <= 9999 {
if (0..=9999).contains(&year) {
write!(f, "{:04}-W{:02}", year, week)
} else {
// ISO 8601 requires the explicit sign for out-of-range years
@@ -142,22 +162,72 @@ impl fmt::Debug for IsoWeek {
#[cfg(test)]
mod tests {
use naive::{internals, MAX_DATE, MIN_DATE};
use Datelike;
#[cfg(feature = "rkyv-validation")]
use super::IsoWeek;
use crate::Datelike;
use crate::naive::date::{self, NaiveDate};
#[test]
fn test_iso_week_extremes() {
let minweek = MIN_DATE.iso_week();
let maxweek = MAX_DATE.iso_week();
let minweek = NaiveDate::MIN.iso_week();
let maxweek = NaiveDate::MAX.iso_week();
assert_eq!(minweek.year(), internals::MIN_YEAR);
assert_eq!(minweek.year(), date::MIN_YEAR);
assert_eq!(minweek.week(), 1);
assert_eq!(minweek.week0(), 0);
assert_eq!(format!("{:?}", minweek), MIN_DATE.format("%G-W%V").to_string());
#[cfg(feature = "alloc")]
assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string());
assert_eq!(maxweek.year(), internals::MAX_YEAR + 1);
assert_eq!(maxweek.year(), date::MAX_YEAR + 1);
assert_eq!(maxweek.week(), 1);
assert_eq!(maxweek.week0(), 0);
assert_eq!(format!("{:?}", maxweek), MAX_DATE.format("%G-W%V").to_string());
#[cfg(feature = "alloc")]
assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string());
}
#[test]
fn test_iso_week_equivalence_for_first_week() {
let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap();
let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
assert_eq!(monday.iso_week(), friday.iso_week());
}
#[test]
fn test_iso_week_equivalence_for_last_week() {
let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap();
let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap();
assert_eq!(monday.iso_week(), friday.iso_week());
}
#[test]
fn test_iso_week_ordering_for_first_week() {
let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap();
let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
assert!(monday.iso_week() >= friday.iso_week());
assert!(monday.iso_week() <= friday.iso_week());
}
#[test]
fn test_iso_week_ordering_for_last_week() {
let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap();
let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap();
assert!(monday.iso_week() >= friday.iso_week());
assert!(monday.iso_week() <= friday.iso_week());
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let minweek = NaiveDate::MIN.iso_week();
let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap();
assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), minweek);
let maxweek = NaiveDate::MAX.iso_week();
let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap();
assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), maxweek);
}
}

281
third_party/rust/chrono/src/naive/mod.rs vendored Normal file
View File

@@ -0,0 +1,281 @@
//! Date and time types unconcerned with timezones.
//!
//! They are primarily building blocks for other types
//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)),
//! but can be also used for the simpler date and time handling.
use core::ops::RangeInclusive;
use crate::Weekday;
use crate::expect;
pub(crate) mod date;
pub(crate) mod datetime;
mod internals;
pub(crate) mod isoweek;
pub(crate) mod time;
#[allow(deprecated)]
pub use self::date::{MAX_DATE, MIN_DATE};
pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator};
#[allow(deprecated)]
pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime};
pub use self::isoweek::IsoWeek;
pub use self::time::NaiveTime;
#[cfg(feature = "__internal_bench")]
#[doc(hidden)]
pub use self::internals::YearFlags as __BenchYearFlags;
/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
/// day of the week.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct NaiveWeek {
date: NaiveDate,
start: Weekday,
}
impl NaiveWeek {
/// Create a new `NaiveWeek`
pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self {
Self { date, start }
}
/// Returns a date representing the first day of the week.
///
/// # Panics
///
/// Panics if the first day of the week happens to fall just out of range of `NaiveDate`
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
/// let week = date.week(Weekday::Mon);
/// assert!(week.first_day() <= date);
/// ```
#[inline]
#[must_use]
pub const fn first_day(&self) -> NaiveDate {
expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`")
}
/// Returns a date representing the first day of the week or
/// `None` if the date is out of `NaiveDate`'s range
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::MIN;
/// let week = date.week(Weekday::Mon);
/// if let Some(first_day) = week.checked_first_day() {
/// assert!(first_day == date);
/// } else {
/// // error handling code
/// return;
/// };
/// ```
#[inline]
#[must_use]
pub const fn checked_first_day(&self) -> Option<NaiveDate> {
let start = self.start.num_days_from_monday() as i32;
let ref_day = self.date.weekday().num_days_from_monday() as i32;
// Calculate the number of days to subtract from `self.date`.
// Do not construct an intermediate date beyond `self.date`, because that may be out of
// range if `date` is close to `NaiveDate::MAX`.
let days = start - ref_day - if start > ref_day { 7 } else { 0 };
self.date.add_days(days)
}
/// Returns a date representing the last day of the week.
///
/// # Panics
///
/// Panics if the last day of the week happens to fall just out of range of `NaiveDate`
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
/// let week = date.week(Weekday::Mon);
/// assert!(week.last_day() >= date);
/// ```
#[inline]
#[must_use]
pub const fn last_day(&self) -> NaiveDate {
expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`")
}
/// Returns a date representing the last day of the week or
/// `None` if the date is out of `NaiveDate`'s range
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::MAX;
/// let week = date.week(Weekday::Mon);
/// if let Some(last_day) = week.checked_last_day() {
/// assert!(last_day == date);
/// } else {
/// // error handling code
/// return;
/// };
/// ```
#[inline]
#[must_use]
pub const fn checked_last_day(&self) -> Option<NaiveDate> {
let end = self.start.pred().num_days_from_monday() as i32;
let ref_day = self.date.weekday().num_days_from_monday() as i32;
// Calculate the number of days to add to `self.date`.
// Do not construct an intermediate date before `self.date` (like with `first_day()`),
// because that may be out of range if `date` is close to `NaiveDate::MIN`.
let days = end - ref_day + if end < ref_day { 7 } else { 0 };
self.date.add_days(days)
}
/// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
/// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions.
///
/// # Panics
///
/// Panics if the either the first or last day of the week happens to fall just out of range of
/// `NaiveDate` (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
/// let week = date.week(Weekday::Mon);
/// let days = week.days();
/// assert!(days.contains(&date));
/// ```
#[inline]
#[must_use]
pub const fn days(&self) -> RangeInclusive<NaiveDate> {
// `expect` doesn't work because `RangeInclusive` is not `Copy`
match self.checked_days() {
Some(val) => val,
None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"),
}
}
/// Returns an [`Option<RangeInclusive<T>>`] representing the whole week bounded by
/// [checked_first_day](NaiveWeek::checked_first_day) and
/// [checked_last_day](NaiveWeek::checked_last_day) functions.
///
/// Returns `None` if either of the boundaries are out of `NaiveDate`'s range
/// (more than ca. 262,000 years away from common era).
///
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday};
///
/// let date = NaiveDate::MAX;
/// let week = date.week(Weekday::Mon);
/// let _days = match week.checked_days() {
/// Some(d) => d,
/// None => {
/// // error handling code
/// return;
/// }
/// };
/// ```
#[inline]
#[must_use]
pub const fn checked_days(&self) -> Option<RangeInclusive<NaiveDate>> {
match (self.checked_first_day(), self.checked_last_day()) {
(Some(first), Some(last)) => Some(first..=last),
(_, _) => None,
}
}
}
/// A duration in calendar days.
///
/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)`
/// doesn't increment the day value as expected due to it being a fixed number of seconds. This
/// difference applies only when dealing with `DateTime<TimeZone>` data types and in other cases
/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Days(pub(crate) u64);
impl Days {
/// Construct a new `Days` from a number of days
pub const fn new(num: u64) -> Self {
Self(num)
}
}
/// Serialization/Deserialization of `NaiveDateTime` in alternate formats
///
/// The various modules in here are intended to be used with serde's [`with` annotation] to
/// serialize as something other than the default ISO 8601 format.
///
/// [`with` annotation]: https://serde.rs/field-attrs.html#with
#[cfg(feature = "serde")]
pub mod serde {
pub use super::datetime::serde::*;
}
#[cfg(test)]
mod test {
use crate::{NaiveDate, Weekday};
#[test]
fn test_naiveweek() {
let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap();
let asserts = [
(Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"),
(Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"),
(Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"),
(Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"),
(Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"),
(Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"),
(Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"),
];
for (start, first_day, last_day) in asserts {
let week = date.week(start);
let days = week.days();
assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d"));
assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d"));
assert!(days.contains(&date));
}
}
#[test]
fn test_naiveweek_min_max() {
let date_max = NaiveDate::MAX;
assert!(date_max.week(Weekday::Mon).first_day() <= date_max);
let date_min = NaiveDate::MIN;
assert!(date_min.week(Weekday::Mon).last_day() >= date_min);
}
#[test]
fn test_naiveweek_checked_no_panic() {
let date_max = NaiveDate::MAX;
if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() {
assert!(last == date_max);
}
let date_min = NaiveDate::MIN;
if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() {
assert!(first == date_min);
}
let _ = date_min.week(Weekday::Mon).checked_days();
let _ = date_max.week(Weekday::Mon).checked_days();
}
}

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,143 @@
use super::NaiveTime;
use core::fmt;
use serde::{de, ser};
// TODO not very optimized for space (binary formats would want something better)
// TODO round-trip for general leap seconds (not just those with second = 60)
impl ser::Serialize for NaiveTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(&self)
}
}
struct NaiveTimeVisitor;
impl de::Visitor<'_> for NaiveTimeVisitor {
type Value = NaiveTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a formatted time string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(E::custom)
}
}
impl<'de> de::Deserialize<'de> for NaiveTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(NaiveTimeVisitor)
}
}
#[cfg(test)]
mod tests {
use crate::NaiveTime;
#[test]
fn test_serde_serialize() {
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(),
Some(r#""00:00:00""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(),
Some(r#""00:00:00.950""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(),
Some(r#""00:00:60""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(),
Some(r#""00:01:02""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(),
Some(r#""03:05:07.098765432""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(),
Some(r#""07:08:09""#.into())
);
assert_eq!(
serde_json::to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(),
Some(r#""12:34:56.000789""#.into())
);
let leap = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap();
assert_eq!(serde_json::to_string(&leap).ok(), Some(r#""23:59:60.999999999""#.into()));
}
#[test]
fn test_serde_deserialize() {
let from_str = serde_json::from_str::<NaiveTime>;
assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
assert_eq!(
from_str(r#""00:00:00.950""#).ok(),
Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap())
);
assert_eq!(
from_str(r#""0:0:0.95""#).ok(),
Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap())
);
assert_eq!(
from_str(r#""00:00:60""#).ok(),
Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap())
);
assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap()));
assert_eq!(
from_str(r#""03:05:07.098765432""#).ok(),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap())
);
assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap()));
assert_eq!(
from_str(r#""12:34:56.000789""#).ok(),
Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap())
);
assert_eq!(
from_str(r#""23:59:60.999999999""#).ok(),
Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap())
);
assert_eq!(
from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored
Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap())
);
// bad formats
assert!(from_str(r#""""#).is_err());
assert!(from_str(r#""000000""#).is_err());
assert!(from_str(r#""00:00:61""#).is_err());
assert!(from_str(r#""00:60:00""#).is_err());
assert!(from_str(r#""24:00:00""#).is_err());
assert!(from_str(r#""23:59:59,1""#).is_err());
assert!(from_str(r#""012:34:56""#).is_err());
assert!(from_str(r#""hh:mm:ss""#).is_err());
assert!(from_str(r#"0"#).is_err());
assert!(from_str(r#"86399"#).is_err());
assert!(from_str(r#"{}"#).is_err());
}
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
let encoded = serialize(&t).unwrap();
let decoded: NaiveTime = deserialize(&encoded).unwrap();
assert_eq!(t, decoded);
}
}

View File

@@ -0,0 +1,393 @@
use super::NaiveTime;
use crate::{FixedOffset, TimeDelta, Timelike};
#[test]
fn test_time_from_hms_milli() {
assert_eq!(
NaiveTime::from_hms_milli_opt(3, 5, 7, 0),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap())
);
assert_eq!(
NaiveTime::from_hms_milli_opt(3, 5, 7, 777),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap())
);
assert_eq!(
NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999),
Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap())
);
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None);
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None);
}
#[test]
fn test_time_from_hms_micro() {
assert_eq!(
NaiveTime::from_hms_micro_opt(3, 5, 7, 0),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap())
);
assert_eq!(
NaiveTime::from_hms_micro_opt(3, 5, 7, 333),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 333_000).unwrap())
);
assert_eq!(
NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777),
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap())
);
assert_eq!(
NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999),
Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap())
);
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None);
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None);
}
#[test]
fn test_time_hms() {
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().hour(), 3);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(0),
Some(NaiveTime::from_hms_opt(0, 5, 7).unwrap())
);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(23),
Some(NaiveTime::from_hms_opt(23, 5, 7).unwrap())
);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(24), None);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(u32::MAX), None);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().minute(), 5);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(0),
Some(NaiveTime::from_hms_opt(3, 0, 7).unwrap())
);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(59),
Some(NaiveTime::from_hms_opt(3, 59, 7).unwrap())
);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(60), None);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(u32::MAX), None);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().second(), 7);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(0),
Some(NaiveTime::from_hms_opt(3, 5, 0).unwrap())
);
assert_eq!(
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(59),
Some(NaiveTime::from_hms_opt(3, 5, 59).unwrap())
);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(60), None);
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(u32::MAX), None);
}
#[test]
fn test_time_add() {
macro_rules! check {
($lhs:expr, $rhs:expr, $sum:expr) => {{
assert_eq!($lhs + $rhs, $sum);
//assert_eq!($rhs + $lhs, $sum);
}};
}
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900));
check!(hmsm(3, 5, 59, 900), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 6, 0, 0));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-1800).unwrap(), hmsm(3, 5, 58, 500));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-800).unwrap(), hmsm(3, 5, 59, 500));
check!(
hmsm(3, 5, 59, 1_300),
TimeDelta::try_milliseconds(-100).unwrap(),
hmsm(3, 5, 59, 1_200)
);
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 5, 59, 1_400));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(1800).unwrap(), hmsm(3, 6, 1, 100));
check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap
check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900));
check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300));
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300));
// regression tests for #37
check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-990).unwrap(), hmsm(23, 59, 59, 10));
check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-9990).unwrap(), hmsm(23, 59, 50, 10));
}
#[test]
fn test_time_overflowing_add() {
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()),
(hmsm(14, 4, 5, 678), 0)
);
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()),
(hmsm(2, 4, 5, 678), 86_400)
);
assert_eq!(
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()),
(hmsm(20, 4, 5, 678), -86_400)
);
// overflowing_add_signed with leap seconds may be counter-intuitive
assert_eq!(
hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(1).unwrap()),
(hmsm(3, 4, 59, 678), 86_400)
);
assert_eq!(
hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(-1).unwrap()),
(hmsm(3, 5, 0, 678), -86_400)
);
}
#[test]
fn test_time_addassignment() {
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
let mut time = hms(12, 12, 12);
time += TimeDelta::try_hours(10).unwrap();
assert_eq!(time, hms(22, 12, 12));
time += TimeDelta::try_hours(10).unwrap();
assert_eq!(time, hms(8, 12, 12));
}
#[test]
fn test_time_subassignment() {
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
let mut time = hms(12, 12, 12);
time -= TimeDelta::try_hours(10).unwrap();
assert_eq!(time, hms(2, 12, 12));
time -= TimeDelta::try_hours(10).unwrap();
assert_eq!(time, hms(16, 12, 12));
}
#[test]
fn test_time_sub() {
macro_rules! check {
($lhs:expr, $rhs:expr, $diff:expr) => {{
// `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration`
assert_eq!($lhs.signed_duration_since($rhs), $diff);
assert_eq!($rhs.signed_duration_since($lhs), -$diff);
}};
}
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero());
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::try_milliseconds(300).unwrap());
check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap());
check!(
hmsm(3, 5, 7, 200),
hmsm(2, 4, 6, 300),
TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::try_milliseconds(900).unwrap()
);
// treats the leap second as if it coincides with the prior non-leap second,
// as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence.
check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::try_milliseconds(400).unwrap());
//check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::try_milliseconds(1400).unwrap());
//check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::try_milliseconds(1400).unwrap());
// additional equality: `time1 + duration = time2` is equivalent to
// `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second.
assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200));
//assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200));
}
#[test]
fn test_core_duration_ops() {
use core::time::Duration;
let mut t = NaiveTime::from_hms_opt(11, 34, 23).unwrap();
let same = t + Duration::ZERO;
assert_eq!(t, same);
t += Duration::new(3600, 0);
assert_eq!(t, NaiveTime::from_hms_opt(12, 34, 23).unwrap());
t -= Duration::new(7200, 0);
assert_eq!(t, NaiveTime::from_hms_opt(10, 34, 23).unwrap());
}
#[test]
fn test_time_fmt() {
assert_eq!(
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 999).unwrap()),
"23:59:59.999"
);
assert_eq!(
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap()),
"23:59:60"
);
assert_eq!(
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_001).unwrap()),
"23:59:60.001"
);
assert_eq!(
format!("{}", NaiveTime::from_hms_micro_opt(0, 0, 0, 43210).unwrap()),
"00:00:00.043210"
);
assert_eq!(
format!("{}", NaiveTime::from_hms_nano_opt(0, 0, 0, 6543210).unwrap()),
"00:00:00.006543210"
);
// the format specifier should have no effect on `NaiveTime`
assert_eq!(
format!("{:30}", NaiveTime::from_hms_milli_opt(3, 5, 7, 9).unwrap()),
"03:05:07.009"
);
}
#[test]
fn test_time_from_str() {
// valid cases
let valid = [
"0:0:0",
"0:0:0.0000000",
"0:0:0.0000003",
" 4 : 3 : 2.1 ",
" 09:08:07 ",
" 09:08 ",
" 9:8:07 ",
"01:02:03",
"4:3:2.1",
"9:8:7",
"09:8:7",
"9:08:7",
"9:8:07",
"09:08:7",
"09:8:07",
"09:08:7",
"9:08:07",
"09:08:07",
"9:8:07.123",
"9:08:7.123",
"09:8:7.123",
"09:08:7.123",
"9:08:07.123",
"09:8:07.123",
"09:08:07.123",
"09:08:07.123",
"09:08:07.1234",
"09:08:07.12345",
"09:08:07.123456",
"09:08:07.1234567",
"09:08:07.12345678",
"09:08:07.123456789",
"09:08:07.1234567891",
"09:08:07.12345678912",
"23:59:60.373929310237",
];
for &s in &valid {
eprintln!("test_time_parse_from_str valid {:?}", s);
let d = match s.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
let s_ = format!("{:?}", d);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
);
}
// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
let invalid = [
"", // empty
"x", // invalid
"15", // missing data
"15:8:", // trailing colon
"15:8:x", // invalid data
"15:8:9x", // invalid data
"23:59:61", // invalid second (out of bounds)
"23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime)
"23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime)
"1441497364.649", // valid datetime, not a NaiveTime
"+1441497364.649", // valid datetime, not a NaiveTime
"+1441497364", // valid datetime, not a NaiveTime
"001:02:03", // invalid hour
"01:002:03", // invalid minute
"01:02:003", // invalid second
"12:34:56.x", // invalid fraction
"12:34:56. 0", // invalid fraction format
"09:08:00000000007", // invalid second / invalid fraction format
];
for &s in &invalid {
eprintln!("test_time_parse_from_str invalid {:?}", s);
assert!(s.parse::<NaiveTime>().is_err());
}
}
#[test]
fn test_time_parse_from_str() {
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
assert_eq!(
NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(hms(12, 34, 56))
); // ignore date and offset
assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0)));
assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0)));
assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0)));
assert_eq!(
NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"),
Ok(hms(12, 59, 0))
);
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok());
assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok());
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok());
assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err());
}
#[test]
fn test_overflowing_offset() {
let hmsm = |h, m, s, n| NaiveTime::from_hms_milli_opt(h, m, s, n).unwrap();
let positive_offset = FixedOffset::east_opt(4 * 60 * 60).unwrap();
// regular time
let t = hmsm(5, 6, 7, 890);
assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(9, 6, 7, 890), 0));
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(1, 6, 7, 890), 0));
// leap second is preserved, and wrap to next day
let t = hmsm(23, 59, 59, 1_000);
assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(3, 59, 59, 1_000), 1));
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(19, 59, 59, 1_000), 0));
// wrap to previous day
let t = hmsm(1, 2, 3, 456);
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(21, 2, 3, 456), -1));
// an odd offset
let negative_offset = FixedOffset::west_opt(((2 * 60) + 3) * 60 + 4).unwrap();
let t = hmsm(5, 6, 7, 890);
assert_eq!(t.overflowing_add_offset(negative_offset), (hmsm(3, 3, 3, 890), 0));
assert_eq!(t.overflowing_sub_offset(negative_offset), (hmsm(7, 9, 11, 890), 0));
assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset);
assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset);
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let t_min = NaiveTime::MIN;
let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveTime>(&bytes).unwrap(), t_min);
let t_max = NaiveTime::MAX;
let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap();
assert_eq!(rkyv::from_bytes::<NaiveTime>(&bytes).unwrap(), t_max);
}

View File

@@ -4,22 +4,29 @@
//! The time zone which has a fixed offset from UTC.
use core::fmt;
use core::ops::{Add, Sub};
use oldtime::Duration as OldDuration;
use core::str::FromStr;
use super::{LocalResult, Offset, TimeZone};
use div::div_mod_floor;
use naive::{NaiveDate, NaiveDateTime, NaiveTime};
use DateTime;
use Timelike;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::{MappedLocalTime, Offset, TimeZone};
use crate::format::{OUT_OF_RANGE, ParseError, scan};
use crate::naive::{NaiveDate, NaiveDateTime};
/// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
///
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
/// on a `FixedOffset` struct is the preferred way to construct
/// `DateTime<FixedOffset>` instances. See the [`east`](#method.east) and
/// [`west`](#method.west) methods for examples.
/// `DateTime<FixedOffset>` instances. See the [`east_opt`](#method.east_opt) and
/// [`west_opt`](#method.west_opt) methods for examples.
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq)),
archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
pub struct FixedOffset {
local_minus_utc: i32,
}
@@ -29,16 +36,8 @@ impl FixedOffset {
/// The negative `secs` means the Western Hemisphere.
///
/// Panics on the out-of-bound `secs`.
///
/// # Example
///
/// ~~~~
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime = FixedOffset::east(5 * hour).ymd(2016, 11, 08)
/// .and_hms(0, 0, 0);
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
/// ~~~~
#[deprecated(since = "0.4.23", note = "use `east_opt()` instead")]
#[must_use]
pub fn east(secs: i32) -> FixedOffset {
FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds")
}
@@ -47,7 +46,20 @@ impl FixedOffset {
/// The negative `secs` means the Western Hemisphere.
///
/// Returns `None` on the out-of-bound `secs`.
pub fn east_opt(secs: i32) -> Option<FixedOffset> {
///
/// # Example
///
/// ```
/// # #[cfg(feature = "alloc")] {
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime =
/// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
/// # }
/// ```
#[must_use]
pub const fn east_opt(secs: i32) -> Option<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: secs })
} else {
@@ -59,16 +71,8 @@ impl FixedOffset {
/// The negative `secs` means the Eastern Hemisphere.
///
/// Panics on the out-of-bound `secs`.
///
/// # Example
///
/// ~~~~
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime = FixedOffset::west(5 * hour).ymd(2016, 11, 08)
/// .and_hms(0, 0, 0);
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
/// ~~~~
#[deprecated(since = "0.4.23", note = "use `west_opt()` instead")]
#[must_use]
pub fn west(secs: i32) -> FixedOffset {
FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds")
}
@@ -77,7 +81,20 @@ impl FixedOffset {
/// The negative `secs` means the Eastern Hemisphere.
///
/// Returns `None` on the out-of-bound `secs`.
pub fn west_opt(secs: i32) -> Option<FixedOffset> {
///
/// # Example
///
/// ```
/// # #[cfg(feature = "alloc")] {
/// use chrono::{FixedOffset, TimeZone};
/// let hour = 3600;
/// let datetime =
/// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
/// # }
/// ```
#[must_use]
pub const fn west_opt(secs: i32) -> Option<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: -secs })
} else {
@@ -87,17 +104,26 @@ impl FixedOffset {
/// Returns the number of seconds to add to convert from UTC to the local time.
#[inline]
pub fn local_minus_utc(&self) -> i32 {
pub const fn local_minus_utc(&self) -> i32 {
self.local_minus_utc
}
/// Returns the number of seconds to add to convert from the local time to UTC.
#[inline]
pub fn utc_minus_local(&self) -> i32 {
pub const fn utc_minus_local(&self) -> i32 {
-self.local_minus_utc
}
}
/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime).
impl FromStr for FixedOffset {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?;
Self::east_opt(offset).ok_or(OUT_OF_RANGE)
}
}
impl TimeZone for FixedOffset {
type Offset = FixedOffset;
@@ -105,11 +131,11 @@ impl TimeZone for FixedOffset {
*offset
}
fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
LocalResult::Single(*self)
fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime<FixedOffset> {
MappedLocalTime::Single(*self)
}
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
LocalResult::Single(*self)
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
MappedLocalTime::Single(*self)
}
fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset {
@@ -130,8 +156,10 @@ impl fmt::Debug for FixedOffset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let offset = self.local_minus_utc;
let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) };
let (mins, sec) = div_mod_floor(offset, 60);
let (hour, min) = div_mod_floor(mins, 60);
let sec = offset.rem_euclid(60);
let mins = offset.div_euclid(60);
let min = mins.rem_euclid(60);
let hour = mins.div_euclid(60);
if sec == 0 {
write!(f, "{}{:02}:{:02}", sign, hour, min)
} else {
@@ -146,99 +174,63 @@ impl fmt::Display for FixedOffset {
}
}
// addition or subtraction of FixedOffset to/from Timelike values is the same as
// adding or subtracting the offset's local_minus_utc value
// but keep keeps the leap second information.
// this should be implemented more efficiently, but for the time being, this is generic right now.
fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
where
T: Timelike + Add<OldDuration, Output = T>,
{
// extract and temporarily remove the fractional part and later recover it
let nanos = lhs.nanosecond();
let lhs = lhs.with_nanosecond(0).unwrap();
(lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
}
impl Add<FixedOffset> for NaiveTime {
type Output = NaiveTime;
#[inline]
fn add(self, rhs: FixedOffset) -> NaiveTime {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl Sub<FixedOffset> for NaiveTime {
type Output = NaiveTime;
#[inline]
fn sub(self, rhs: FixedOffset) -> NaiveTime {
add_with_leapsecond(&self, -rhs.local_minus_utc)
}
}
impl Add<FixedOffset> for NaiveDateTime {
type Output = NaiveDateTime;
#[inline]
fn add(self, rhs: FixedOffset) -> NaiveDateTime {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl Sub<FixedOffset> for NaiveDateTime {
type Output = NaiveDateTime;
#[inline]
fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
add_with_leapsecond(&self, -rhs.local_minus_utc)
}
}
impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
type Output = DateTime<Tz>;
#[inline]
fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
add_with_leapsecond(&self, rhs.local_minus_utc)
}
}
impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
type Output = DateTime<Tz>;
#[inline]
fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
add_with_leapsecond(&self, -rhs.local_minus_utc)
#[cfg(all(feature = "arbitrary", feature = "std"))]
impl arbitrary::Arbitrary<'_> for FixedOffset {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<FixedOffset> {
let secs = u.int_in_range(-86_399..=86_399)?;
let fixed_offset = FixedOffset::east_opt(secs)
.expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous.");
Ok(fixed_offset)
}
}
#[cfg(test)]
mod tests {
use super::FixedOffset;
use offset::TimeZone;
use crate::offset::TimeZone;
use std::str::FromStr;
#[test]
fn test_date_extreme_offset() {
// starting from 0.3 we don't have an offset exceeding one day.
// this makes everything easier!
let offset = FixedOffset::east_opt(86399).unwrap();
assert_eq!(
format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)),
"2012-02-29+23:59:59".to_string()
format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()),
"2012-02-29T05:06:07+23:59:59"
);
let offset = FixedOffset::east_opt(-86399).unwrap();
assert_eq!(
format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)),
"2012-02-29T05:06:07+23:59:59".to_string()
format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()),
"2012-02-29T05:06:07-23:59:59"
);
let offset = FixedOffset::west_opt(86399).unwrap();
assert_eq!(
format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)),
"2012-03-04-23:59:59".to_string()
format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()),
"2012-03-04T05:06:07-23:59:59"
);
let offset = FixedOffset::west_opt(-86399).unwrap();
assert_eq!(
format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)),
"2012-03-04T05:06:07-23:59:59".to_string()
format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()),
"2012-03-04T05:06:07+23:59:59"
);
}
#[test]
fn test_parse_offset() {
let offset = FixedOffset::from_str("-0500").unwrap();
assert_eq!(offset.local_minus_utc, -5 * 3600);
let offset = FixedOffset::from_str("-08:00").unwrap();
assert_eq!(offset.local_minus_utc, -8 * 3600);
let offset = FixedOffset::from_str("+06:30").unwrap();
assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800);
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let offset = FixedOffset::from_str("-0500").unwrap();
let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap();
assert_eq!(rkyv::from_bytes::<FixedOffset>(&bytes).unwrap(), offset);
}
}

View File

@@ -1,227 +0,0 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
//! The local (system) time zone.
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
use sys::{self, Timespec};
use super::fixed::FixedOffset;
use super::{LocalResult, TimeZone};
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
use naive::NaiveTime;
use naive::{NaiveDate, NaiveDateTime};
use {Date, DateTime};
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
use {Datelike, Timelike};
/// Converts a `time::Tm` struct into the timezone-aware `DateTime`.
/// This assumes that `time` is working correctly, i.e. any error is fatal.
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
fn tm_to_datetime(mut tm: sys::Tm) -> DateTime<Local> {
if tm.tm_sec >= 60 {
tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000;
tm.tm_sec = 59;
}
#[cfg(not(windows))]
fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate {
// from_yo is more efficient than from_ymd (since it's the internal representation).
NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1)
}
#[cfg(windows)]
fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate {
// ...but tm_yday is broken in Windows (issue #85)
NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32)
}
let date = tm_to_naive_date(&tm);
let time = NaiveTime::from_hms_nano(
tm.tm_hour as u32,
tm.tm_min as u32,
tm.tm_sec as u32,
tm.tm_nsec as u32,
);
let offset = FixedOffset::east(tm.tm_utcoff);
DateTime::from_utc(date.and_time(time) - offset, offset)
}
/// Converts a local `NaiveDateTime` to the `time::Timespec`.
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec {
// well, this exploits an undocumented `Tm::to_timespec` behavior
// to get the exact function we want (either `timegm` or `mktime`).
// the number 1 is arbitrary but should be non-zero to trigger `mktime`.
let tm_utcoff = if local { 1 } else { 0 };
let tm = sys::Tm {
tm_sec: d.second() as i32,
tm_min: d.minute() as i32,
tm_hour: d.hour() as i32,
tm_mday: d.day() as i32,
tm_mon: d.month0() as i32, // yes, C is that strange...
tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`.
tm_wday: 0, // to_local ignores this
tm_yday: 0, // and this
tm_isdst: -1,
tm_utcoff: tm_utcoff,
// do not set this, OS APIs are heavily inconsistent in terms of leap second handling
tm_nsec: 0,
};
tm.to_timespec()
}
/// The local timescale. This is implemented via the standard `time` crate.
///
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
/// on the Local struct is the preferred way to construct `DateTime<Local>`
/// instances.
///
/// # Example
///
/// ~~~~
/// use chrono::{Local, DateTime, TimeZone};
///
/// let dt: DateTime<Local> = Local::now();
/// let dt: DateTime<Local> = Local.timestamp(0, 0);
/// ~~~~
#[derive(Copy, Clone, Debug)]
pub struct Local;
impl Local {
/// Returns a `Date` which corresponds to the current date.
pub fn today() -> Date<Local> {
Local::now().date()
}
/// Returns a `DateTime` which corresponds to the current date.
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
pub fn now() -> DateTime<Local> {
tm_to_datetime(Timespec::now().local())
}
/// Returns a `DateTime` which corresponds to the current date.
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
pub fn now() -> DateTime<Local> {
use super::Utc;
let now: DateTime<Utc> = super::Utc::now();
// Workaround missing timezone logic in `time` crate
let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60);
DateTime::from_utc(now.naive_utc(), offset)
}
}
impl TimeZone for Local {
type Offset = FixedOffset;
fn from_offset(_offset: &FixedOffset) -> Local {
Local
}
// they are easier to define in terms of the finished date and time unlike other offsets
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<FixedOffset> {
self.from_local_date(local).map(|date| *date.offset())
}
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
self.from_local_datetime(local).map(|datetime| *datetime.offset())
}
fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
*self.from_utc_date(utc).offset()
}
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
*self.from_utc_datetime(utc).offset()
}
// override them for avoiding redundant works
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Local>> {
// this sounds very strange, but required for keeping `TimeZone::ymd` sane.
// in the other words, we use the offset at the local midnight
// but keep the actual date unaltered (much like `FixedOffset`).
let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0));
midnight.map(|datetime| Date::from_utc(*local, *datetime.offset()))
}
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let mut local = local.clone();
// Get the offset from the js runtime
let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60);
local -= ::Duration::seconds(offset.local_minus_utc() as i64);
LocalResult::Single(DateTime::from_utc(local, offset))
}
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let timespec = datetime_to_timespec(local, true);
// datetime_to_timespec completely ignores leap seconds, so we need to adjust for them
let mut tm = timespec.local();
assert_eq!(tm.tm_nsec, 0);
tm.tm_nsec = local.nanosecond() as i32;
LocalResult::Single(tm_to_datetime(tm))
}
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0));
Date::from_utc(*utc, *midnight.offset())
}
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
// Get the offset from the js runtime
let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60);
DateTime::from_utc(*utc, offset)
}
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
let timespec = datetime_to_timespec(utc, false);
// datetime_to_timespec completely ignores leap seconds, so we need to adjust for them
let mut tm = timespec.local();
assert_eq!(tm.tm_nsec, 0);
tm.tm_nsec = utc.nanosecond() as i32;
tm_to_datetime(tm)
}
}
#[cfg(test)]
mod tests {
use super::Local;
use offset::TimeZone;
use Datelike;
#[test]
fn test_local_date_sanity_check() {
// issue #27
assert_eq!(Local.ymd(2999, 12, 28).day(), 28);
}
#[test]
fn test_leap_second() {
// issue #123
let today = Local::today();
let dt = today.and_hms_milli(1, 2, 59, 1000);
let timestr = dt.time().to_string();
// the OS API may or may not support the leap second,
// but there are only two sensible options.
assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr);
let dt = today.and_hms_milli(1, 2, 3, 1234);
let timestr = dt.time().to_string();
assert!(
timestr == "01:02:03.234" || timestr == "01:02:04.234",
"unexpected timestr {:?}",
timestr
);
}
}

View File

@@ -0,0 +1,541 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
//! The local (system) time zone.
#[cfg(windows)]
use std::cmp::Ordering;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::fixed::FixedOffset;
use super::{MappedLocalTime, TimeZone};
#[allow(deprecated)]
use crate::Date;
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
use crate::{DateTime, Utc};
#[cfg(unix)]
#[path = "unix.rs"]
mod inner;
#[cfg(windows)]
#[path = "windows.rs"]
mod inner;
#[cfg(all(windows, feature = "clock"))]
#[allow(unreachable_pub)]
mod win_bindings;
#[cfg(all(
not(unix),
not(windows),
not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))
))]
mod inner {
use crate::{FixedOffset, MappedLocalTime, NaiveDateTime};
pub(super) fn offset_from_utc_datetime(
_utc_time: &NaiveDateTime,
) -> MappedLocalTime<FixedOffset> {
MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
}
pub(super) fn offset_from_local_datetime(
_local_time: &NaiveDateTime,
) -> MappedLocalTime<FixedOffset> {
MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
}
}
#[cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
mod inner {
use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike};
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset();
MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
}
pub(super) fn offset_from_local_datetime(
local: &NaiveDateTime,
) -> MappedLocalTime<FixedOffset> {
let mut year = local.year();
if year < 100 {
// The API in `js_sys` does not let us create a `Date` with negative years.
// And values for years from `0` to `99` map to the years `1900` to `1999`.
// Shift the value by a multiple of 400 years until it is `>= 100`.
let shift_cycles = (year - 100).div_euclid(400);
year -= shift_cycles * 400;
}
let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec(
year as u32,
local.month0() as i32,
local.day() as i32,
local.hour() as i32,
local.minute() as i32,
local.second() as i32,
// ignore milliseconds, our representation of leap seconds may be problematic
);
let offset = js_date.get_timezone_offset();
// We always get a result, even if this time does not exist or is ambiguous.
MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
}
}
#[cfg(unix)]
mod tz_info;
/// The local timescale.
///
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
/// on the Local struct is the preferred way to construct `DateTime<Local>`
/// instances.
///
/// # Example
///
/// ```
/// use chrono::{DateTime, Local, TimeZone};
///
/// let dt1: DateTime<Local> = Local::now();
/// let dt2: DateTime<Local> = Local.timestamp_opt(0, 0).unwrap();
/// assert!(dt1 >= dt2);
/// ```
#[derive(Copy, Clone, Debug)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq)),
archive_attr(derive(Clone, Copy, Debug))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Local;
impl Local {
/// Returns a `Date` which corresponds to the current date.
#[deprecated(since = "0.4.23", note = "use `Local::now()` instead")]
#[allow(deprecated)]
#[must_use]
pub fn today() -> Date<Local> {
Local::now().date()
}
/// Returns a `DateTime<Local>` which corresponds to the current date, time and offset from
/// UTC.
///
/// See also the similar [`Utc::now()`] which returns `DateTime<Utc>`, i.e. without the local
/// offset.
///
/// # Example
///
/// ```
/// # #![allow(unused_variables)]
/// # use chrono::{DateTime, FixedOffset, Local};
/// // Current local time
/// let now = Local::now();
///
/// // Current local date
/// let today = now.date_naive();
///
/// // Current local time, converted to `DateTime<FixedOffset>`
/// let now_fixed_offset = Local::now().fixed_offset();
/// // or
/// let now_fixed_offset: DateTime<FixedOffset> = Local::now().into();
///
/// // Current time in some timezone (let's use +05:00)
/// // Note that it is usually more efficient to use `Utc::now` for this use case.
/// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
/// let now_with_offset = Local::now().with_timezone(&offset);
/// ```
pub fn now() -> DateTime<Local> {
Utc::now().with_timezone(&Local)
}
}
impl TimeZone for Local {
type Offset = FixedOffset;
fn from_offset(_offset: &FixedOffset) -> Local {
Local
}
#[allow(deprecated)]
fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<FixedOffset> {
// Get the offset at local midnight.
self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN))
}
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
inner::offset_from_local_datetime(local)
}
#[allow(deprecated)]
fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
// Get the offset at midnight.
self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN))
}
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
inner::offset_from_utc_datetime(utc).unwrap()
}
}
#[cfg(windows)]
#[derive(Copy, Clone, Eq, PartialEq)]
struct Transition {
transition_utc: NaiveDateTime,
offset_before: FixedOffset,
offset_after: FixedOffset,
}
#[cfg(windows)]
impl Transition {
fn new(
transition_local: NaiveDateTime,
offset_before: FixedOffset,
offset_after: FixedOffset,
) -> Transition {
// It is no problem if the transition time in UTC falls a couple of hours inside the buffer
// space around the `NaiveDateTime` range (although it is very theoretical to have a
// transition at midnight around `NaiveDate::(MIN|MAX)`.
let transition_utc = transition_local.overflowing_sub_offset(offset_before);
Transition { transition_utc, offset_before, offset_after }
}
}
#[cfg(windows)]
impl PartialOrd for Transition {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.transition_utc.cmp(&other.transition_utc))
}
}
#[cfg(windows)]
impl Ord for Transition {
fn cmp(&self, other: &Self) -> Ordering {
self.transition_utc.cmp(&other.transition_utc)
}
}
// Calculate the time in UTC given a local time and transitions.
// `transitions` must be sorted.
#[cfg(windows)]
fn lookup_with_dst_transitions(
transitions: &[Transition],
dt: NaiveDateTime,
) -> MappedLocalTime<FixedOffset> {
for t in transitions.iter() {
// A transition can result in the wall clock time going forward (creating a gap) or going
// backward (creating a fold). We are interested in the earliest and latest wall time of the
// transition, as this are the times between which `dt` does may not exist or is ambiguous.
//
// It is no problem if the transition times falls a couple of hours inside the buffer
// space around the `NaiveDateTime` range (although it is very theoretical to have a
// transition at midnight around `NaiveDate::(MIN|MAX)`.
let (offset_min, offset_max) =
match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() {
true => (t.offset_before, t.offset_after),
false => (t.offset_after, t.offset_before),
};
let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min);
let wall_latest = t.transition_utc.overflowing_add_offset(offset_max);
if dt < wall_earliest {
return MappedLocalTime::Single(t.offset_before);
} else if dt <= wall_latest {
return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) {
Ordering::Equal => MappedLocalTime::Single(t.offset_before),
Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after),
Ordering::Greater => {
if dt == wall_earliest {
MappedLocalTime::Single(t.offset_before)
} else if dt == wall_latest {
MappedLocalTime::Single(t.offset_after)
} else {
MappedLocalTime::None
}
}
};
}
}
MappedLocalTime::Single(transitions.last().unwrap().offset_after)
}
#[cfg(test)]
mod tests {
use super::Local;
use crate::offset::TimeZone;
#[cfg(windows)]
use crate::offset::local::{Transition, lookup_with_dst_transitions};
use crate::{Datelike, Days, Utc};
#[cfg(windows)]
use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime};
#[test]
fn verify_correct_offsets() {
let now = Local::now();
let from_local = Local.from_local_datetime(&now.naive_local()).unwrap();
let from_utc = Local.from_utc_datetime(&now.naive_utc());
assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc());
assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
assert_eq!(now, from_local);
assert_eq!(now, from_utc);
}
#[test]
fn verify_correct_offsets_distant_past() {
let distant_past = Local::now() - Days::new(365 * 500);
let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap();
let from_utc = Local.from_utc_datetime(&distant_past.naive_utc());
assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc());
assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
assert_eq!(distant_past, from_local);
assert_eq!(distant_past, from_utc);
}
#[test]
fn verify_correct_offsets_distant_future() {
let distant_future = Local::now() + Days::new(365 * 35000);
let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap();
let from_utc = Local.from_utc_datetime(&distant_future.naive_utc());
assert_eq!(
distant_future.offset().local_minus_utc(),
from_local.offset().local_minus_utc()
);
assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
assert_eq!(distant_future, from_local);
assert_eq!(distant_future, from_utc);
}
#[test]
fn test_local_date_sanity_check() {
// issue #27
assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28);
}
#[test]
fn test_leap_second() {
// issue #123
let today = Utc::now().date_naive();
if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) {
let timestr = dt.time().to_string();
// the OS API may or may not support the leap second,
// but there are only two sensible options.
assert!(
timestr == "15:02:60" || timestr == "15:03:00",
"unexpected timestr {:?}",
timestr
);
}
if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) {
let timestr = dt.time().to_string();
assert!(
timestr == "15:02:03.234" || timestr == "15:02:04.234",
"unexpected timestr {:?}",
timestr
);
}
}
#[test]
#[cfg(windows)]
fn test_lookup_with_dst_transitions() {
let ymdhms = |y, m, d, h, n, s| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()
};
#[track_caller]
#[allow(clippy::too_many_arguments)]
fn compare_lookup(
transitions: &[Transition],
y: i32,
m: u32,
d: u32,
h: u32,
n: u32,
s: u32,
result: MappedLocalTime<FixedOffset>,
) {
let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
assert_eq!(lookup_with_dst_transitions(transitions, dt), result);
}
// dst transition before std transition
// dst offset > std offset
let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
let transitions = [
Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst),
Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std),
];
compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std));
// std transition before dst transition
// dst offset > std offset
let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap();
let transitions = [
Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std),
Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst),
];
compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None);
compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst));
// dst transition before std transition
// dst offset < std offset
let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap();
let transitions = [
Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst),
Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std),
];
compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None);
compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
// std transition before dst transition
// dst offset < std offset
let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap();
let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
let transitions = [
Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std),
Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst),
];
compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None);
compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
// offset stays the same
let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
let transitions = [
Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std),
Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std),
];
compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
// single transition
let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)];
compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
}
#[test]
#[cfg(windows)]
fn test_lookup_with_dst_transitions_limits() {
// Transition beyond UTC year end doesn't panic in year of `NaiveDate::MAX`
let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
let transitions = [
Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst),
Transition::new(NaiveDateTime::MAX, dst, std),
];
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()),
MappedLocalTime::Single(std)
);
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()),
MappedLocalTime::Single(dst)
);
// Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when
// converted to UTC).
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX),
MappedLocalTime::Ambiguous(dst, std)
);
// Transition before UTC year end doesn't panic in year of `NaiveDate::MIN`
let std = FixedOffset::west_opt(3 * 60 * 60).unwrap();
let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap();
let transitions = [
Transition::new(NaiveDateTime::MIN, std, dst),
Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std),
];
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()),
MappedLocalTime::Single(dst)
);
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()),
MappedLocalTime::Single(std)
);
// Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when
// converted to UTC).
assert_eq!(
lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN),
MappedLocalTime::Ambiguous(std, dst)
);
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let local = Local;
// Local is a ZST and serializes to 0 bytes
let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap();
assert_eq!(bytes.len(), 0);
// but is deserialized to an archived variant without a
// wrapping object
assert_eq!(rkyv::from_bytes::<Local>(&bytes).unwrap(), super::ArchivedLocal);
}
}

View File

@@ -0,0 +1,116 @@
#![deny(missing_docs)]
#![allow(dead_code)]
#![warn(unreachable_pub)]
use std::num::ParseIntError;
use std::str::Utf8Error;
use std::time::SystemTimeError;
use std::{error, fmt, io};
mod timezone;
pub(crate) use timezone::TimeZone;
mod parser;
mod rule;
/// Unified error type for everything in the crate
#[derive(Debug)]
pub(crate) enum Error {
/// Date time error
DateTime(&'static str),
/// Local time type search error
FindLocalTimeType(&'static str),
/// Local time type error
LocalTimeType(&'static str),
/// Invalid slice for integer conversion
InvalidSlice(&'static str),
/// Invalid Tzif file
InvalidTzFile(&'static str),
/// Invalid TZ string
InvalidTzString(&'static str),
/// I/O error
Io(io::Error),
/// Out of range error
OutOfRange(&'static str),
/// Integer parsing error
ParseInt(ParseIntError),
/// Date time projection error
ProjectDateTime(&'static str),
/// System time error
SystemTime(SystemTimeError),
/// Time zone error
TimeZone(&'static str),
/// Transition rule error
TransitionRule(&'static str),
/// Unsupported Tzif file
UnsupportedTzFile(&'static str),
/// Unsupported TZ string
UnsupportedTzString(&'static str),
/// UTF-8 error
Utf8(Utf8Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
DateTime(error) => write!(f, "invalid date time: {}", error),
FindLocalTimeType(error) => error.fmt(f),
LocalTimeType(error) => write!(f, "invalid local time type: {}", error),
InvalidSlice(error) => error.fmt(f),
InvalidTzString(error) => write!(f, "invalid TZ string: {}", error),
InvalidTzFile(error) => error.fmt(f),
Io(error) => error.fmt(f),
OutOfRange(error) => error.fmt(f),
ParseInt(error) => error.fmt(f),
ProjectDateTime(error) => error.fmt(f),
SystemTime(error) => error.fmt(f),
TransitionRule(error) => write!(f, "invalid transition rule: {}", error),
TimeZone(error) => write!(f, "invalid time zone: {}", error),
UnsupportedTzFile(error) => error.fmt(f),
UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error),
Utf8(error) => error.fmt(f),
}
}
}
impl error::Error for Error {}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::Io(error)
}
}
impl From<ParseIntError> for Error {
fn from(error: ParseIntError) -> Self {
Error::ParseInt(error)
}
}
impl From<SystemTimeError> for Error {
fn from(error: SystemTimeError) -> Self {
Error::SystemTime(error)
}
}
impl From<Utf8Error> for Error {
fn from(error: Utf8Error) -> Self {
Error::Utf8(error)
}
}
/// Number of hours in one day
const HOURS_PER_DAY: i64 = 24;
/// Number of seconds in one hour
const SECONDS_PER_HOUR: i64 = 3600;
/// Number of seconds in one day
const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY;
/// Number of days in one week
const DAYS_PER_WEEK: i64 = 7;
/// Month days in a normal year
const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
/// Cumulated month days in a normal year
const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] =
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime};
use super::tz_info::TimeZone;
use super::{FixedOffset, NaiveDateTime};
use crate::MappedLocalTime;
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
offset(utc, false)
}
pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
offset(local, true)
}
fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime<FixedOffset> {
TZ_INFO.with(|maybe_cache| {
maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local)
})
}
// we have to store the `Cache` in an option as it can't
// be initialized in a static context.
thread_local! {
static TZ_INFO: RefCell<Option<Cache>> = Default::default();
}
enum Source {
LocalTime { mtime: SystemTime },
Environment { hash: u64 },
}
impl Source {
fn new(env_tz: Option<&str>) -> Source {
match env_tz {
Some(tz) => {
let mut hasher = hash_map::DefaultHasher::new();
hasher.write(tz.as_bytes());
let hash = hasher.finish();
Source::Environment { hash }
}
None => match fs::symlink_metadata("/etc/localtime") {
Ok(data) => Source::LocalTime {
// we have to pick a sensible default when the mtime fails
// by picking SystemTime::now() we raise the probability of
// the cache being invalidated if/when the mtime starts working
mtime: data.modified().unwrap_or_else(|_| SystemTime::now()),
},
Err(_) => {
// as above, now() should be a better default than some constant
// TODO: see if we can improve caching in the case where the fallback is a valid timezone
Source::LocalTime { mtime: SystemTime::now() }
}
},
}
}
}
struct Cache {
zone: TimeZone,
source: Source,
last_checked: SystemTime,
}
#[cfg(target_os = "aix")]
const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";
#[cfg(not(any(target_os = "android", target_os = "aix")))]
const TZDB_LOCATION: &str = "/usr/share/zoneinfo";
fn fallback_timezone() -> Option<TimeZone> {
let tz_name = iana_time_zone::get_timezone().ok()?;
#[cfg(not(target_os = "android"))]
let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?;
#[cfg(target_os = "android")]
let bytes = android_tzdata::find_tz_data(&tz_name).ok()?;
TimeZone::from_tz_data(&bytes).ok()
}
impl Default for Cache {
fn default() -> Cache {
// default to UTC if no local timezone can be found
let env_tz = env::var("TZ").ok();
let env_ref = env_tz.as_deref();
Cache {
last_checked: SystemTime::now(),
source: Source::new(env_ref),
zone: current_zone(env_ref),
}
}
}
fn current_zone(var: Option<&str>) -> TimeZone {
TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc)
}
impl Cache {
fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime<FixedOffset> {
let now = SystemTime::now();
match now.duration_since(self.last_checked) {
// If the cache has been around for less than a second then we reuse it
// unconditionally. This is a reasonable tradeoff because the timezone
// generally won't be changing _that_ often, but if the time zone does
// change, it will reflect sufficiently quickly from an application
// user's perspective.
Ok(d) if d.as_secs() < 1 => (),
Ok(_) | Err(_) => {
let env_tz = env::var("TZ").ok();
let env_ref = env_tz.as_deref();
let new_source = Source::new(env_ref);
let out_of_date = match (&self.source, &new_source) {
// change from env to file or file to env, must recreate the zone
(Source::Environment { .. }, Source::LocalTime { .. })
| (Source::LocalTime { .. }, Source::Environment { .. }) => true,
// stay as file, but mtime has changed
(Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime })
if old_mtime != mtime =>
{
true
}
// stay as env, but hash of variable has changed
(Source::Environment { hash: old_hash }, Source::Environment { hash })
if old_hash != hash =>
{
true
}
// cache can be reused
_ => false,
};
if out_of_date {
self.zone = current_zone(env_ref);
}
self.last_checked = now;
self.source = new_source;
}
}
if !local {
let offset = self
.zone
.find_local_time_type(d.and_utc().timestamp())
.expect("unable to select local time type")
.offset();
return match FixedOffset::east_opt(offset) {
Some(offset) => MappedLocalTime::Single(offset),
None => MappedLocalTime::None,
};
}
// we pass through the year as the year of a local point in time must either be valid in that locale, or
// the entire time was skipped in which case we will return MappedLocalTime::None anyway.
self.zone
.find_local_time_type_from_local(d)
.expect("unable to select local time type")
.and_then(|o| FixedOffset::east_opt(o.offset()))
}
}

View File

@@ -0,0 +1,49 @@
#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)]
windows_link::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL);
windows_link::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL);
windows_link::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL);
windows_link::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL);
pub type BOOL = i32;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct DYNAMIC_TIME_ZONE_INFORMATION {
pub Bias: i32,
pub StandardName: [u16; 32],
pub StandardDate: SYSTEMTIME,
pub StandardBias: i32,
pub DaylightName: [u16; 32],
pub DaylightDate: SYSTEMTIME,
pub DaylightBias: i32,
pub TimeZoneKeyName: [u16; 128],
pub DynamicDaylightTimeDisabled: bool,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct FILETIME {
pub dwLowDateTime: u32,
pub dwHighDateTime: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SYSTEMTIME {
pub wYear: u16,
pub wMonth: u16,
pub wDayOfWeek: u16,
pub wDay: u16,
pub wHour: u16,
pub wMinute: u16,
pub wSecond: u16,
pub wMilliseconds: u16,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TIME_ZONE_INFORMATION {
pub Bias: i32,
pub StandardName: [u16; 32],
pub StandardDate: SYSTEMTIME,
pub StandardBias: i32,
pub DaylightName: [u16; 32],
pub DaylightDate: SYSTEMTIME,
pub DaylightBias: i32,
}

View File

@@ -0,0 +1,7 @@
--out src/offset/local/win_bindings.rs
--flat --sys --no-comment
--filter
GetTimeZoneInformationForYear
SystemTimeToFileTime
SystemTimeToTzSpecificLocalTime
TzSpecificLocalTimeToSystemTime

View File

@@ -0,0 +1,293 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::cmp::Ordering;
use std::mem::MaybeUninit;
use std::ptr;
use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION};
use crate::offset::local::{Transition, lookup_with_dst_transitions};
use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday};
// We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates
// as Chrono. Also it really isn't that difficult to work out the correct offset from the provided
// DST rules.
//
// This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC
// falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is
// very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`.
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
// Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be
// using the rules for the year of the corresponding local time. But this matches what
// `SystemTimeToTzSpecificLocalTime` is documented to do.
let tz_info = match TzInfo::for_year(utc.year()) {
Some(tz_info) => tz_info,
None => return MappedLocalTime::None,
};
let offset = match (tz_info.std_transition, tz_info.dst_transition) {
(Some(std_transition), Some(dst_transition)) => {
let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset);
let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset);
if dst_transition_utc < std_transition_utc {
match utc >= &dst_transition_utc && utc < &std_transition_utc {
true => tz_info.dst_offset,
false => tz_info.std_offset,
}
} else {
match utc >= &std_transition_utc && utc < &dst_transition_utc {
true => tz_info.std_offset,
false => tz_info.dst_offset,
}
}
}
(Some(std_transition), None) => {
let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset);
match utc < &std_transition_utc {
true => tz_info.dst_offset,
false => tz_info.std_offset,
}
}
(None, Some(dst_transition)) => {
let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset);
match utc < &dst_transition_utc {
true => tz_info.std_offset,
false => tz_info.dst_offset,
}
}
(None, None) => tz_info.std_offset,
};
MappedLocalTime::Single(offset)
}
// We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle
// ambiguous cases (during a DST transition). Instead we get the timezone information for the
// current year and compute it ourselves, like we do on Unix.
pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
let tz_info = match TzInfo::for_year(local.year()) {
Some(tz_info) => tz_info,
None => return MappedLocalTime::None,
};
// Create a sorted slice of transitions and use `lookup_with_dst_transitions`.
match (tz_info.std_transition, tz_info.dst_transition) {
(Some(std_transition), Some(dst_transition)) => {
let std_transition =
Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset);
let dst_transition =
Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset);
let transitions = match std_transition.cmp(&dst_transition) {
Ordering::Less => [std_transition, dst_transition],
Ordering::Greater => [dst_transition, std_transition],
Ordering::Equal => {
// This doesn't make sense. Let's just return the standard offset.
return MappedLocalTime::Single(tz_info.std_offset);
}
};
lookup_with_dst_transitions(&transitions, *local)
}
(Some(std_transition), None) => {
let transitions =
[Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset)];
lookup_with_dst_transitions(&transitions, *local)
}
(None, Some(dst_transition)) => {
let transitions =
[Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset)];
lookup_with_dst_transitions(&transitions, *local)
}
(None, None) => MappedLocalTime::Single(tz_info.std_offset),
}
}
// The basis for Windows timezone and DST support has been in place since Windows 2000. It does not
// allow for complex rules like the IANA timezone database:
// - A timezone has the same base offset the whole year.
// - There seem to be either zero or two DST transitions (but we support having just one).
// - As of Vista(?) only years from 2004 until a few years into the future are supported.
// - All other years get the base settings, which seem to be that of the current year.
//
// These details don't matter much, we just work with the offsets and transition dates Windows
// returns through `GetTimeZoneInformationForYear` for a particular year.
struct TzInfo {
// Offset from UTC during standard time.
std_offset: FixedOffset,
// Offset from UTC during daylight saving time.
dst_offset: FixedOffset,
// Transition from standard time to daylight saving time, given in local standard time.
std_transition: Option<NaiveDateTime>,
// Transition from daylight saving time to standard time, given in local daylight saving time.
dst_transition: Option<NaiveDateTime>,
}
impl TzInfo {
fn for_year(year: i32) -> Option<TzInfo> {
// The API limits years to 1601..=30827.
// Working with timezones and daylight saving time this far into the past or future makes
// little sense. But whatever is extrapolated for 1601 or 30827 is what can be extrapolated
// for years beyond.
let ref_year = year.clamp(1601, 30827) as u16;
let tz_info = unsafe {
let mut tz_info = MaybeUninit::<TIME_ZONE_INFORMATION>::uninit();
if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 {
return None;
}
tz_info.assume_init()
};
let std_offset = (tz_info.Bias)
.checked_add(tz_info.StandardBias)
.and_then(|o| o.checked_mul(60))
.and_then(FixedOffset::west_opt)?;
let dst_offset = (tz_info.Bias)
.checked_add(tz_info.DaylightBias)
.and_then(|o| o.checked_mul(60))
.and_then(FixedOffset::west_opt)?;
Some(TzInfo {
std_offset,
dst_offset,
std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?,
dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?,
})
}
}
/// Resolve a `SYSTEMTIME` object to an `Option<NaiveDateTime>`.
///
/// A `SYSTEMTIME` within a `TIME_ZONE_INFORMATION` struct can be zero to indicate there is no
/// transition.
/// If it has year, month and day values it is a concrete date.
/// If the year is missing the `SYSTEMTIME` is a rule, which this method resolves for the provided
/// year. A rule has a month, weekday, and nth weekday of the month as components.
///
/// Returns `Err` if any of the values is invalid, which should never happen.
fn naive_date_time_from_system_time(
st: SYSTEMTIME,
year: i32,
) -> Result<Option<NaiveDateTime>, ()> {
if st.wYear == 0 && st.wMonth == 0 {
return Ok(None);
}
let time = NaiveTime::from_hms_milli_opt(
st.wHour as u32,
st.wMinute as u32,
st.wSecond as u32,
st.wMilliseconds as u32,
)
.ok_or(())?;
if st.wYear != 0 {
// We have a concrete date.
let date =
NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).ok_or(())?;
return Ok(Some(date.and_time(time)));
}
// Resolve a rule with month, weekday, and nth weekday of the month to a date in the current
// year.
let weekday = match st.wDayOfWeek {
0 => Weekday::Sun,
1 => Weekday::Mon,
2 => Weekday::Tue,
3 => Weekday::Wed,
4 => Weekday::Thu,
5 => Weekday::Fri,
6 => Weekday::Sat,
_ => return Err(()),
};
let nth_day = match st.wDay {
1..=5 => st.wDay as u8,
_ => return Err(()),
};
let date = NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, nth_day)
.or_else(|| NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, 4))
.ok_or(())?; // `st.wMonth` must be invalid
Ok(Some(date.and_time(time)))
}
#[cfg(test)]
mod tests {
use crate::offset::local::win_bindings::{
FILETIME, SYSTEMTIME, SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime,
};
use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta};
use crate::{Datelike, TimeZone, Timelike};
use std::mem::MaybeUninit;
use std::ptr;
#[test]
fn verify_against_tz_specific_local_time_to_system_time() {
// The implementation in Windows itself is the source of truth on how to work with the OS
// timezone information. This test compares for every hour over a period of 125 years our
// implementation to `TzSpecificLocalTimeToSystemTime`.
//
// This uses parts of a previous Windows `Local` implementation in chrono.
fn from_local_time(dt: &NaiveDateTime) -> DateTime<Local> {
let st = system_time_from_naive_date_time(dt);
let utc_time = local_to_utc_time(&st);
let utc_secs = system_time_as_unix_seconds(&utc_time);
let local_secs = system_time_as_unix_seconds(&st);
let offset = (local_secs - utc_secs) as i32;
let offset = FixedOffset::east_opt(offset).unwrap();
DateTime::from_naive_utc_and_offset(*dt - offset, offset)
}
fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {
SYSTEMTIME {
// Valid values: 1601-30827
wYear: dt.year() as u16,
// Valid values:1-12
wMonth: dt.month() as u16,
// Valid values: 0-6, starting Sunday.
// NOTE: enum returns 1-7, starting Monday, so we are
// off here, but this is not currently used in local.
wDayOfWeek: dt.weekday() as u16,
// Valid values: 1-31
wDay: dt.day() as u16,
// Valid values: 0-23
wHour: dt.hour() as u16,
// Valid values: 0-59
wMinute: dt.minute() as u16,
// Valid values: 0-59
wSecond: dt.second() as u16,
// Valid values: 0-999
wMilliseconds: 0,
}
}
fn local_to_utc_time(local: &SYSTEMTIME) -> SYSTEMTIME {
let mut sys_time = MaybeUninit::<SYSTEMTIME>::uninit();
unsafe { TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()) };
// SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can
// assume the value is initialized.
unsafe { sys_time.assume_init() }
}
const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> i64 {
let mut init = MaybeUninit::<FILETIME>::uninit();
unsafe {
SystemTimeToFileTime(st, init.as_mut_ptr());
}
// SystemTimeToFileTime must have succeeded at this point, so we can assume the value is
// initialized.
let filetime = unsafe { init.assume_init() };
let bit_shift =
((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64);
(bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC
}
let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 30, 0).unwrap();
while date.year() < 2078 {
// Windows doesn't handle non-existing dates, it just treats it as valid.
if let Some(our_result) = Local.from_local_datetime(&date).earliest() {
assert_eq!(from_local_time(&date), our_result);
}
date += TimeDelta::try_hours(1).unwrap();
}
}
}

View File

@@ -20,71 +20,144 @@
use core::fmt;
use format::{parse, ParseResult, Parsed, StrftimeItems};
use naive::{NaiveDate, NaiveDateTime, NaiveTime};
use Weekday;
use {Date, DateTime};
use crate::Weekday;
use crate::format::{ParseResult, Parsed, StrftimeItems, parse};
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
#[allow(deprecated)]
use crate::{Date, DateTime};
/// The conversion result from the local time to the timezone-aware datetime types.
pub(crate) mod fixed;
pub use self::fixed::FixedOffset;
#[cfg(feature = "clock")]
pub(crate) mod local;
#[cfg(feature = "clock")]
pub use self::local::Local;
pub(crate) mod utc;
pub use self::utc::Utc;
/// The result of mapping a local time to a concrete instant in a given time zone.
///
/// The calculation to go from a local time (wall clock time) to an instant in UTC can end up in
/// three cases:
/// * A single, simple result.
/// * An ambiguous result when the clock is turned backwards during a transition due to for example
/// DST.
/// * No result when the clock is turned forwards during a transition due to for example DST.
///
/// When the clock is turned backwards it creates a _fold_ in local time, during which the local
/// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during
/// which the local time is _missing_, or does not exist.
///
/// Chrono does not return a default choice or invalid data during time zone transitions, but has
/// the `MappedLocalTime` type to help deal with the result correctly.
///
/// The type of `T` is usually a [`DateTime`] but may also be only an offset.
pub type MappedLocalTime<T> = LocalResult<T>;
#[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)]
/// Old name of [`MappedLocalTime`]. See that type for more documentation.
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.
/// The local time maps to a single unique result.
Single(T),
/// Given local time representation has multiple results and thus ambiguous.
/// This can occur when, for example, the negative timezone transition.
Ambiguous(T /*min*/, T /*max*/),
/// The local time is _ambiguous_ because there is a _fold_ in the local time.
///
/// This variant contains the two possible results, in the order `(earliest, latest)`.
Ambiguous(T, T),
/// The local time does not exist because there is a _gap_ in the local time.
///
/// This variant may also be returned if there was an error while resolving the local time,
/// caused by for example missing time zone data files, an error in an OS API, or overflow.
None,
}
impl<T> LocalResult<T> {
/// Returns `Some` only when the conversion result is unique, or `None` otherwise.
impl<T> MappedLocalTime<T> {
/// Returns `Some` if the time zone mapping has a single result.
///
/// # Errors
///
/// Returns `None` if local time falls in a _fold_ or _gap_ in the local time, or if there was
/// an error.
#[must_use]
pub fn single(self) -> Option<T> {
match self {
LocalResult::Single(t) => Some(t),
MappedLocalTime::Single(t) => Some(t),
_ => None,
}
}
/// Returns `Some` for the earliest possible conversion result, or `None` if none.
/// Returns the earliest possible result of the time zone mapping.
///
/// # Errors
///
/// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error.
#[must_use]
pub fn earliest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t),
MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t),
_ => None,
}
}
/// Returns `Some` for the latest possible conversion result, or `None` if none.
/// Returns the latest possible result of the time zone mapping.
///
/// # Errors
///
/// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error.
#[must_use]
pub fn latest(self) -> Option<T> {
match self {
LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t),
MappedLocalTime::Single(t) | MappedLocalTime::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> {
/// Maps a `MappedLocalTime<T>` into `MappedLocalTime<U>` with given function.
#[must_use]
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> MappedLocalTime<U> {
match self {
LocalResult::None => LocalResult::None,
LocalResult::Single(v) => LocalResult::Single(f(v)),
LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)),
MappedLocalTime::None => MappedLocalTime::None,
MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)),
MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)),
}
}
/// Maps a `MappedLocalTime<T>` into `MappedLocalTime<U>` with given function.
///
/// Returns `MappedLocalTime::None` if the function returns `None`.
#[must_use]
pub(crate) fn and_then<U, F: FnMut(T) -> Option<U>>(self, mut f: F) -> MappedLocalTime<U> {
match self {
MappedLocalTime::None => MappedLocalTime::None,
MappedLocalTime::Single(v) => match f(v) {
Some(new) => MappedLocalTime::Single(new),
None => MappedLocalTime::None,
},
MappedLocalTime::Ambiguous(min, max) => match (f(min), f(max)) {
(Some(min), Some(max)) => MappedLocalTime::Ambiguous(min, max),
_ => MappedLocalTime::None,
},
}
}
}
impl<Tz: TimeZone> LocalResult<Date<Tz>> {
#[allow(deprecated)]
impl<Tz: TimeZone> MappedLocalTime<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>> {
#[must_use]
pub fn and_time(self, time: NaiveTime) -> MappedLocalTime<DateTime<Tz>> {
match self {
LocalResult::Single(d) => {
d.and_time(time).map_or(LocalResult::None, LocalResult::Single)
MappedLocalTime::Single(d) => {
d.and_time(time).map_or(MappedLocalTime::None, MappedLocalTime::Single)
}
_ => LocalResult::None,
_ => MappedLocalTime::None,
}
}
@@ -93,12 +166,13 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> {
#[must_use]
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> MappedLocalTime<DateTime<Tz>> {
match self {
LocalResult::Single(d) => {
d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single)
MappedLocalTime::Single(d) => {
d.and_hms_opt(hour, min, sec).map_or(MappedLocalTime::None, MappedLocalTime::Single)
}
_ => LocalResult::None,
_ => MappedLocalTime::None,
}
}
@@ -108,18 +182,19 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
#[must_use]
pub fn and_hms_milli_opt(
self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> LocalResult<DateTime<Tz>> {
) -> MappedLocalTime<DateTime<Tz>> {
match self {
LocalResult::Single(d) => d
MappedLocalTime::Single(d) => d
.and_hms_milli_opt(hour, min, sec, milli)
.map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None,
.map_or(MappedLocalTime::None, MappedLocalTime::Single),
_ => MappedLocalTime::None,
}
}
@@ -129,18 +204,19 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
#[must_use]
pub fn and_hms_micro_opt(
self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> LocalResult<DateTime<Tz>> {
) -> MappedLocalTime<DateTime<Tz>> {
match self {
LocalResult::Single(d) => d
MappedLocalTime::Single(d) => d
.and_hms_micro_opt(hour, min, sec, micro)
.map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None,
.map_or(MappedLocalTime::None, MappedLocalTime::Single),
_ => MappedLocalTime::None,
}
}
@@ -150,29 +226,41 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
///
/// Propagates any error. Ambiguous result would be discarded.
#[inline]
#[must_use]
pub fn and_hms_nano_opt(
self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> LocalResult<DateTime<Tz>> {
) -> MappedLocalTime<DateTime<Tz>> {
match self {
LocalResult::Single(d) => d
MappedLocalTime::Single(d) => d
.and_hms_nano_opt(hour, min, sec, nano)
.map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None,
.map_or(MappedLocalTime::None, MappedLocalTime::Single),
_ => MappedLocalTime::None,
}
}
}
impl<T: fmt::Debug> LocalResult<T> {
/// Returns the single unique conversion result, or panics accordingly.
impl<T: fmt::Debug> MappedLocalTime<T> {
/// Returns a single unique conversion result or panics.
///
/// `unwrap()` is best combined with time zone types where the mapping can never fail like
/// [`Utc`] and [`FixedOffset`]. Note that for [`FixedOffset`] there is a rare case where a
/// resulting [`DateTime`] can be out of range.
///
/// # Panics
///
/// Panics if the local time falls within a _fold_ or a _gap_ in the local time, and on any
/// error that may have been returned by the type implementing [`TimeZone`].
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
match self {
LocalResult::None => panic!("No such local time"),
LocalResult::Single(t) => t,
LocalResult::Ambiguous(t1, t2) => {
MappedLocalTime::None => panic!("No such local time"),
MappedLocalTime::Single(t) => t,
MappedLocalTime::Ambiguous(t1, t2) => {
panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2)
}
}
@@ -187,14 +275,34 @@ pub trait Offset: Sized + Clone + fmt::Debug {
/// The time zone.
///
/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and
/// [`DateTime`](../struct.DateTime.html) types.
/// The methods here are the primary constructors for the [`DateTime`] type.
pub trait TimeZone: Sized + Clone {
/// An associated offset type.
/// This type is used to store the actual offset in date and time types.
/// The original `TimeZone` value can be recovered via `TimeZone::from_offset`.
type Offset: Offset;
/// Make a new `DateTime` from year, month, day, time components and current time zone.
///
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
///
/// Returns `MappedLocalTime::None` on invalid input data.
fn with_ymd_and_hms(
&self,
year: i32,
month: u32,
day: u32,
hour: u32,
min: u32,
sec: u32,
) -> MappedLocalTime<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 => MappedLocalTime::None,
}
}
/// Makes a new `Date` from year, month, day and the current time zone.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
///
@@ -202,14 +310,8 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Panics on the out-of-range date, invalid month and/or day.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.ymd(2015, 5, 15).to_string(), "2015-05-15UTC");
/// ~~~~
#[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")]
#[allow(deprecated)]
fn ymd(&self, year: i32, month: u32, day: u32) -> Date<Self> {
self.ymd_opt(year, month, day).unwrap()
}
@@ -221,19 +323,12 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date, invalid month and/or day.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, LocalResult, TimeZone};
///
/// assert_eq!(Utc.ymd_opt(2015, 5, 15).unwrap().to_string(), "2015-05-15UTC");
/// assert_eq!(Utc.ymd_opt(2000, 0, 0), LocalResult::None);
/// ~~~~
fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult<Date<Self>> {
#[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")]
#[allow(deprecated)]
fn ymd_opt(&self, year: i32, month: u32, day: u32) -> MappedLocalTime<Date<Self>> {
match NaiveDate::from_ymd_opt(year, month, day) {
Some(d) => self.from_local_date(&d),
None => LocalResult::None,
None => MappedLocalTime::None,
}
}
@@ -244,14 +339,11 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Panics on the out-of-range date and/or invalid DOY.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.yo(2015, 135).to_string(), "2015-05-15UTC");
/// ~~~~
#[deprecated(
since = "0.4.23",
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
)]
#[allow(deprecated)]
fn yo(&self, year: i32, ordinal: u32) -> Date<Self> {
self.yo_opt(year, ordinal).unwrap()
}
@@ -263,10 +355,15 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date and/or invalid DOY.
fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult<Date<Self>> {
#[deprecated(
since = "0.4.23",
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
)]
#[allow(deprecated)]
fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime<Date<Self>> {
match NaiveDate::from_yo_opt(year, ordinal) {
Some(d) => self.from_local_date(&d),
None => LocalResult::None,
None => MappedLocalTime::None,
}
}
@@ -279,14 +376,11 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Panics on the out-of-range date and/or invalid week number.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, Weekday, TimeZone};
///
/// assert_eq!(Utc.isoywd(2015, 20, Weekday::Fri).to_string(), "2015-05-15UTC");
/// ~~~~
#[deprecated(
since = "0.4.23",
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
)]
#[allow(deprecated)]
fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date<Self> {
self.isoywd_opt(year, week, weekday).unwrap()
}
@@ -300,10 +394,15 @@ pub trait TimeZone: Sized + Clone {
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date and/or invalid week number.
fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult<Date<Self>> {
#[deprecated(
since = "0.4.23",
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
)]
#[allow(deprecated)]
fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> MappedLocalTime<Date<Self>> {
match NaiveDate::from_isoywd_opt(year, week, weekday) {
Some(d) => self.from_local_date(&d),
None => LocalResult::None,
None => MappedLocalTime::None,
}
}
@@ -311,16 +410,15 @@ pub trait TimeZone: Sized + Clone {
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
/// and the number of nanoseconds since the last whole non-leap second.
///
/// The nanosecond part can exceed 1,000,000,000 in order to represent a
/// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`.
/// (The true "UNIX timestamp" cannot represent a leap second unambiguously.)
///
/// # Panics
///
/// Panics on the out-of-range number of seconds and/or invalid nanosecond,
/// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt).
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.timestamp(1431648000, 0).to_string(), "2015-05-15 00:00:00 UTC");
/// ~~~~
#[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")]
fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime<Self> {
self.timestamp_opt(secs, nsecs).unwrap()
}
@@ -329,12 +427,26 @@ pub trait TimeZone: Sized + Clone {
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
/// and the number of nanoseconds since the last whole non-leap second.
///
/// Returns `LocalResult::None` on out-of-range number of seconds and/or
/// invalid nanosecond, otherwise always returns `LocalResult::Single`.
fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult<DateTime<Self>> {
match NaiveDateTime::from_timestamp_opt(secs, nsecs) {
Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
None => LocalResult::None,
/// The nanosecond part can exceed 1,000,000,000 in order to represent a
/// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`.
/// (The true "UNIX timestamp" cannot represent a leap second unambiguously.)
///
/// # Errors
///
/// Returns `MappedLocalTime::None` on out-of-range number of seconds and/or
/// invalid nanosecond, otherwise always returns `MappedLocalTime::Single`.
///
/// # Example
///
/// ```
/// use chrono::{TimeZone, Utc};
///
/// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC");
/// ```
fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp(secs, nsecs) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
}
@@ -343,14 +455,7 @@ pub trait TimeZone: Sized + Clone {
///
/// Panics on out-of-range number of milliseconds for a non-panicking
/// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt).
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone};
///
/// assert_eq!(Utc.timestamp_millis(1431648000).timestamp(), 1431648);
/// ~~~~
#[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")]
fn timestamp_millis(&self, millis: i64) -> DateTime<Self> {
self.timestamp_millis_opt(millis).unwrap()
}
@@ -359,60 +464,78 @@ pub trait TimeZone: Sized + Clone {
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
///
///
/// Returns `LocalResult::None` on out-of-range number of milliseconds
/// Returns `MappedLocalTime::None` on out-of-range number of milliseconds
/// and/or invalid nanosecond, otherwise always returns
/// `LocalResult::Single`.
/// `MappedLocalTime::Single`.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone, LocalResult};
/// ```
/// use chrono::{MappedLocalTime, TimeZone, Utc};
/// match Utc.timestamp_millis_opt(1431648000) {
/// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648),
/// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648),
/// _ => panic!("Incorrect timestamp_millis"),
/// };
/// ~~~~
fn timestamp_millis_opt(&self, millis: i64) -> LocalResult<DateTime<Self>> {
let (mut secs, mut millis) = (millis / 1000, millis % 1000);
if millis < 0 {
secs -= 1;
millis += 1000;
/// ```
fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp_millis(millis) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
self.timestamp_opt(secs, millis as u32 * 1_000_000)
}
/// Makes a new `DateTime` from the number of non-leap nanoseconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
///
/// Unlike [`timestamp_millis`](#method.timestamp_millis), this never
/// panics.
/// Unlike [`timestamp_millis_opt`](#method.timestamp_millis_opt), this never fails.
///
/// # Example
///
/// ~~~~
/// use chrono::{Utc, TimeZone};
/// ```
/// use chrono::{TimeZone, Utc};
///
/// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648);
/// ~~~~
/// ```
fn timestamp_nanos(&self, nanos: i64) -> DateTime<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()
self.from_utc_datetime(&DateTime::from_timestamp_nanos(nanos).naive_utc())
}
/// Parses a string with the specified format string and
/// returns a `DateTime` with the current offset.
/// See the [`format::strftime` module](../format/strftime/index.html)
/// on the supported escape sequences.
/// Makes a new `DateTime` from the number of non-leap microseconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
///
/// If the format does not include offsets, the current offset is assumed;
/// otherwise the input should have a matching UTC offset.
/// # Example
///
/// See also `DateTime::parse_from_str` which gives a local `DateTime`
/// with parsed `FixedOffset`.
/// ```
/// use chrono::{TimeZone, Utc};
///
/// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648);
/// ```
fn timestamp_micros(&self, micros: i64) -> MappedLocalTime<DateTime<Self>> {
match DateTime::from_timestamp_micros(micros) {
Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())),
None => MappedLocalTime::None,
}
}
/// Parses a string with the specified format string and returns a
/// `DateTime` with the current offset.
///
/// See the [`crate::format::strftime`] module on the
/// supported escape sequences.
///
/// If the to-be-parsed string includes an offset, it *must* match the
/// offset of the TimeZone, otherwise an error will be returned.
///
/// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with
/// parsed [`FixedOffset`].
///
/// See also [`NaiveDateTime::parse_from_str`] which gives a [`NaiveDateTime`] without
/// an offset, but can be converted to a [`DateTime`] with [`NaiveDateTime::and_utc`] or
/// [`NaiveDateTime::and_local_timezone`].
#[deprecated(
since = "0.4.29",
note = "use `DateTime::parse_from_str` or `NaiveDateTime::parse_from_str` with `and_utc()` or `and_local_timezone()` instead"
)]
fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult<DateTime<Self>> {
let mut parsed = Parsed::new();
parse(&mut parsed, s, StrftimeItems::new(fmt))?;
@@ -423,13 +546,16 @@ pub trait TimeZone: Sized + Clone {
fn from_offset(offset: &Self::Offset) -> Self;
/// Creates the offset(s) for given local `NaiveDate` if possible.
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset>;
fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<Self::Offset>;
/// Creates the offset(s) for given local `NaiveDateTime` if possible.
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset>;
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<Self::Offset>;
/// Converts the local `NaiveDate` to the timezone-aware `Date` if possible.
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Self>> {
#[allow(clippy::wrong_self_convention)]
#[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")]
#[allow(deprecated)]
fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<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)
@@ -437,9 +563,13 @@ pub trait TimeZone: Sized + Clone {
}
/// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible.
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> {
self.offset_from_local_datetime(local)
.map(|offset| DateTime::from_utc(*local - offset.fix(), offset))
#[allow(clippy::wrong_self_convention)]
fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<DateTime<Self>> {
self.offset_from_local_datetime(local).and_then(|off| {
local
.checked_sub_offset(off.fix())
.map(|dt| DateTime::from_naive_utc_and_offset(dt, off))
})
}
/// Creates the offset for given UTC `NaiveDate`. This cannot fail.
@@ -450,48 +580,68 @@ pub trait TimeZone: Sized + Clone {
/// Converts the UTC `NaiveDate` to the local time.
/// The UTC is continuous and thus this cannot fail (but can give the duplicate local time).
#[allow(clippy::wrong_self_convention)]
#[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")]
#[allow(deprecated)]
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Self> {
Date::from_utc(*utc, self.offset_from_utc_date(utc))
}
/// Converts the UTC `NaiveDateTime` to the local time.
/// The UTC is continuous and thus this cannot fail (but can give the duplicate local time).
#[allow(clippy::wrong_self_convention)]
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc))
DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc))
}
}
mod fixed;
#[cfg(feature = "clock")]
mod local;
mod utc;
pub use self::fixed::FixedOffset;
#[cfg(feature = "clock")]
pub use self::local::Local;
pub use self::utc::Utc;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixed_offset_min_max_dates() {
for offset_hour in -23..=23 {
dbg!(offset_hour);
let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap();
let local_max = offset.from_utc_datetime(&NaiveDateTime::MAX);
assert_eq!(local_max.naive_utc(), NaiveDateTime::MAX);
let local_min = offset.from_utc_datetime(&NaiveDateTime::MIN);
assert_eq!(local_min.naive_utc(), NaiveDateTime::MIN);
let local_max = offset.from_local_datetime(&NaiveDateTime::MAX);
if offset_hour >= 0 {
assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX);
} else {
assert_eq!(local_max, MappedLocalTime::None);
}
let local_min = offset.from_local_datetime(&NaiveDateTime::MIN);
if offset_hour <= 0 {
assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN);
} else {
assert_eq!(local_min, MappedLocalTime::None);
}
}
}
#[test]
fn test_negative_millis() {
let dt = Utc.timestamp_millis(-1000);
let dt = Utc.timestamp_millis_opt(-1000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC");
let dt = Utc.timestamp_millis(-7000);
let dt = Utc.timestamp_millis_opt(-7000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC");
let dt = Utc.timestamp_millis(-7001);
let dt = Utc.timestamp_millis_opt(-7001).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:52.999 UTC");
let dt = Utc.timestamp_millis(-7003);
let dt = Utc.timestamp_millis_opt(-7003).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC");
let dt = Utc.timestamp_millis(-999);
let dt = Utc.timestamp_millis_opt(-999).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC");
let dt = Utc.timestamp_millis(-1);
let dt = Utc.timestamp_millis_opt(-1).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC");
let dt = Utc.timestamp_millis(-60000);
let dt = Utc.timestamp_millis_opt(-60000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC");
let dt = Utc.timestamp_millis(-3600000);
let dt = Utc.timestamp_millis_opt(-3600000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC");
for (millis, expected) in &[
@@ -500,7 +650,7 @@ mod tests {
(-7003, "1969-12-31 23:59:52.997 UTC"),
] {
match Utc.timestamp_millis_opt(*millis) {
LocalResult::Single(dt) => {
MappedLocalTime::Single(dt) => {
assert_eq!(dt.to_string(), *expected);
}
e => panic!("Got {:?} instead of an okay answer", e),
@@ -524,8 +674,22 @@ mod tests {
#[test]
fn test_nanos_never_panics() {
Utc.timestamp_nanos(i64::max_value());
Utc.timestamp_nanos(i64::MAX);
Utc.timestamp_nanos(i64::default());
Utc.timestamp_nanos(i64::min_value());
Utc.timestamp_nanos(i64::MIN);
}
#[test]
fn test_negative_micros() {
let dt = Utc.timestamp_micros(-1_000_000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC");
let dt = Utc.timestamp_micros(-999_999).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000001 UTC");
let dt = Utc.timestamp_micros(-1).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999 UTC");
let dt = Utc.timestamp_micros(-60_000_000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC");
let dt = Utc.timestamp_micros(-3_600_000_000).unwrap();
assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC");
}
}

View File

@@ -4,16 +4,24 @@
//! The UTC (Coordinated Universal Time) time zone.
use core::fmt;
use super::{FixedOffset, LocalResult, Offset, TimeZone};
use naive::{NaiveDate, NaiveDateTime};
#[cfg(all(
feature = "clock",
not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))
feature = "now",
not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))
))]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(feature = "clock")]
use {Date, DateTime};
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::{FixedOffset, MappedLocalTime, Offset, TimeZone};
use crate::naive::{NaiveDate, NaiveDateTime};
#[cfg(feature = "now")]
#[allow(deprecated)]
use crate::{Date, DateTime};
/// The UTC time zone. This is the most efficient time zone when you don't need the local time.
/// It is also used as an offset (which is also a dummy type).
@@ -24,35 +32,79 @@ use {Date, DateTime};
///
/// # Example
///
/// ~~~~
/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc};
/// ```
/// use chrono::{DateTime, TimeZone, Utc};
///
/// let dt = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc);
/// let dt = DateTime::from_timestamp(61, 0).unwrap();
///
/// assert_eq!(Utc.timestamp(61, 0), dt);
/// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt);
/// ~~~~
#[derive(Copy, Clone, PartialEq, Eq)]
/// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt);
/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt);
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq)),
archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
pub struct Utc;
#[cfg(feature = "clock")]
#[cfg(feature = "now")]
impl Utc {
/// Returns a `Date` which corresponds to the current date.
#[deprecated(
since = "0.4.23",
note = "use `Utc::now()` instead, potentially with `.date_naive()`"
)]
#[allow(deprecated)]
#[must_use]
pub fn today() -> Date<Utc> {
Utc::now().date()
}
/// Returns a `DateTime` which corresponds to the current date.
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))]
/// Returns a `DateTime<Utc>` which corresponds to the current date and time in UTC.
///
/// See also the similar [`Local::now()`] which returns `DateTime<Local>`, i.e. the local date
/// and time including offset from UTC.
///
/// [`Local::now()`]: crate::Local::now
///
/// # Example
///
/// ```
/// # #![allow(unused_variables)]
/// # use chrono::{FixedOffset, Utc};
/// // Current time in UTC
/// let now_utc = Utc::now();
///
/// // Current date in UTC
/// let today_utc = now_utc.date_naive();
///
/// // Current time in some timezone (let's use +05:00)
/// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
/// let now_with_offset = Utc::now().with_timezone(&offset);
/// ```
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
#[must_use]
pub fn now() -> DateTime<Utc> {
let now =
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
let naive = NaiveDateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos() as u32);
DateTime::from_utc(naive, Utc)
DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()).unwrap()
}
/// Returns a `DateTime` which corresponds to the current date.
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))]
/// Returns a `DateTime` which corresponds to the current date and time.
#[cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
#[must_use]
pub fn now() -> DateTime<Utc> {
let now = js_sys::Date::new_0();
DateTime::<Utc>::from(now)
@@ -66,11 +118,11 @@ impl TimeZone for Utc {
Utc
}
fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Utc> {
LocalResult::Single(Utc)
fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime<Utc> {
MappedLocalTime::Single(Utc)
}
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Utc> {
LocalResult::Single(Utc)
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime<Utc> {
MappedLocalTime::Single(Utc)
}
fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc {
@@ -83,7 +135,7 @@ impl TimeZone for Utc {
impl Offset for Utc {
fn fix(&self) -> FixedOffset {
FixedOffset::east(0)
FixedOffset::east_opt(0).unwrap()
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,126 +0,0 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Platform wrappers for converting UTC times to and from the local time zone.
//!
//! This code was rescued from v0.1 of the time crate, which is no longer
//! maintained. It has been substantially stripped down to the bare minimum
//! required by chrono.
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(any(target_arch = "wasm32", target_env = "sgx"))]
#[path = "sys/stub.rs"]
mod inner;
#[cfg(unix)]
#[path = "sys/unix.rs"]
mod inner;
#[cfg(windows)]
#[path = "sys/windows.rs"]
mod inner;
/// A record specifying a time value in seconds and nanoseconds, where
/// nanoseconds represent the offset from the given second.
///
/// For example a timespec of 1.2 seconds after the beginning of the epoch would
/// be represented as {sec: 1, nsec: 200000000}.
pub struct Timespec {
pub sec: i64,
pub nsec: i32,
}
impl Timespec {
/// Constructs a timespec representing the current time in UTC.
pub fn now() -> Timespec {
let st =
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 }
}
/// Converts this timespec into the system's local time.
pub fn local(self) -> Tm {
let mut tm = Tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_utcoff: 0,
tm_nsec: 0,
};
inner::time_to_local_tm(self.sec, &mut tm);
tm.tm_nsec = self.nsec;
tm
}
}
/// Holds a calendar date and time broken down into its components (year, month,
/// day, and so on), also called a broken-down time value.
// FIXME: use c_int instead of i32?
#[cfg(feature = "clock")]
#[repr(C)]
pub struct Tm {
/// Seconds after the minute - [0, 60]
pub tm_sec: i32,
/// Minutes after the hour - [0, 59]
pub tm_min: i32,
/// Hours after midnight - [0, 23]
pub tm_hour: i32,
/// Day of the month - [1, 31]
pub tm_mday: i32,
/// Months since January - [0, 11]
pub tm_mon: i32,
/// Years since 1900
pub tm_year: i32,
/// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
pub tm_wday: i32,
/// Days since January 1 - [0, 365]
pub tm_yday: i32,
/// Daylight Saving Time flag.
///
/// This value is positive if Daylight Saving Time is in effect, zero if
/// Daylight Saving Time is not in effect, and negative if this information
/// is not available.
pub tm_isdst: i32,
/// Identifies the time zone that was used to compute this broken-down time
/// value, including any adjustment for Daylight Saving Time. This is the
/// number of seconds east of UTC. For example, for U.S. Pacific Daylight
/// Time, the value is `-7*60*60 = -25200`.
pub tm_utcoff: i32,
/// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
pub tm_nsec: i32,
}
impl Tm {
/// Convert time to the seconds from January 1, 1970
pub fn to_timespec(&self) -> Timespec {
let sec = match self.tm_utcoff {
0 => inner::utc_tm_to_time(self),
_ => inner::local_tm_to_time(self),
};
Timespec { sec: sec, nsec: self.tm_nsec }
}
}

View File

@@ -1,80 +0,0 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use super::Tm;
fn time_to_tm(ts: i64, tm: &mut Tm) {
let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) };
static YTAB: [[i64; 12]; 2] = [
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
];
let mut year = 1970;
let dayclock = ts % 86400;
let mut dayno = ts / 86400;
tm.tm_sec = (dayclock % 60) as i32;
tm.tm_min = ((dayclock % 3600) / 60) as i32;
tm.tm_hour = (dayclock / 3600) as i32;
tm.tm_wday = ((dayno + 4) % 7) as i32;
loop {
let yearsize = if leapyear(year) { 366 } else { 365 };
if dayno >= yearsize {
dayno -= yearsize;
year += 1;
} else {
break;
}
}
tm.tm_year = (year - 1900) as i32;
tm.tm_yday = dayno as i32;
let mut mon = 0;
while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] {
dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon];
mon += 1;
}
tm.tm_mon = mon as i32;
tm.tm_mday = dayno as i32 + 1;
tm.tm_isdst = 0;
}
fn tm_to_time(tm: &Tm) -> i64 {
let mut y = tm.tm_year as i64 + 1900;
let mut m = tm.tm_mon as i64 + 1;
if m <= 2 {
y -= 1;
m += 12;
}
let d = tm.tm_mday as i64;
let h = tm.tm_hour as i64;
let mi = tm.tm_min as i64;
let s = tm.tm_sec as i64;
(365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400
+ 3600 * h
+ 60 * mi
+ s
}
pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
// FIXME: Add timezone logic
time_to_tm(sec, tm);
}
pub fn utc_tm_to_time(tm: &Tm) -> i64 {
tm_to_time(tm)
}
pub fn local_tm_to_time(tm: &Tm) -> i64 {
// FIXME: Add timezone logic
tm_to_time(tm)
}

View File

@@ -1,126 +0,0 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use super::Tm;
use libc::{self, time_t};
use std::io;
use std::mem;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
extern "C" {
static timezone: time_t;
static altzone: time_t;
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
fn tzset() {
extern "C" {
fn tzset();
}
unsafe { tzset() }
}
fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) {
tm.tm_sec = rust_tm.tm_sec;
tm.tm_min = rust_tm.tm_min;
tm.tm_hour = rust_tm.tm_hour;
tm.tm_mday = rust_tm.tm_mday;
tm.tm_mon = rust_tm.tm_mon;
tm.tm_year = rust_tm.tm_year;
tm.tm_wday = rust_tm.tm_wday;
tm.tm_yday = rust_tm.tm_yday;
tm.tm_isdst = rust_tm.tm_isdst;
}
fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) {
rust_tm.tm_sec = tm.tm_sec;
rust_tm.tm_min = tm.tm_min;
rust_tm.tm_hour = tm.tm_hour;
rust_tm.tm_mday = tm.tm_mday;
rust_tm.tm_mon = tm.tm_mon;
rust_tm.tm_year = tm.tm_year;
rust_tm.tm_wday = tm.tm_wday;
rust_tm.tm_yday = tm.tm_yday;
rust_tm.tm_isdst = tm.tm_isdst;
rust_tm.tm_utcoff = utcoff;
}
#[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))]
unsafe fn timegm(tm: *mut libc::tm) -> time_t {
use std::env::{remove_var, set_var, var_os};
extern "C" {
fn tzset();
}
let ret;
let current_tz = var_os("TZ");
set_var("TZ", "UTC");
tzset();
ret = libc::mktime(tm);
if let Some(tz) = current_tz {
set_var("TZ", tz);
} else {
remove_var("TZ");
}
tzset();
ret
}
pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
unsafe {
let sec = sec as time_t;
let mut out = mem::zeroed();
if libc::localtime_r(&sec, &mut out).is_null() {
panic!("localtime_r failed: {}", io::Error::last_os_error());
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let gmtoff = {
tzset();
// < 0 means we don't know; assume we're not in DST.
if out.tm_isdst == 0 {
// timezone is seconds west of UTC, tm_gmtoff is seconds east
-timezone
} else if out.tm_isdst > 0 {
-altzone
} else {
-timezone
}
};
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
let gmtoff = out.tm_gmtoff;
tm_to_rust_tm(&out, gmtoff as i32, tm);
}
}
pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 {
#[cfg(not(any(
all(target_os = "android", target_pointer_width = "32"),
target_os = "nacl",
target_os = "solaris",
target_os = "illumos"
)))]
use libc::timegm;
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
use libc::timegm64 as timegm;
let mut tm = unsafe { mem::zeroed() };
rust_tm_to_tm(rust_tm, &mut tm);
unsafe { timegm(&mut tm) as i64 }
}
pub fn local_tm_to_time(rust_tm: &Tm) -> i64 {
let mut tm = unsafe { mem::zeroed() };
rust_tm_to_tm(rust_tm, &mut tm);
unsafe { libc::mktime(&mut tm) as i64 }
}

View File

@@ -1,131 +0,0 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use super::Tm;
use std::io;
use std::mem;
use winapi::shared::minwindef::*;
use winapi::um::minwinbase::SYSTEMTIME;
use winapi::um::timezoneapi::*;
const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
fn time_to_file_time(sec: i64) -> FILETIME {
let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64;
FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD }
}
fn file_time_as_u64(ft: &FILETIME) -> u64 {
((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64)
}
fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 {
let t = file_time_as_u64(ft) as i64;
((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64
}
fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME {
unsafe {
let mut ft = mem::zeroed();
SystemTimeToFileTime(sys, &mut ft);
ft
}
}
fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME {
let mut sys: SYSTEMTIME = unsafe { mem::zeroed() };
sys.wSecond = tm.tm_sec as WORD;
sys.wMinute = tm.tm_min as WORD;
sys.wHour = tm.tm_hour as WORD;
sys.wDay = tm.tm_mday as WORD;
sys.wDayOfWeek = tm.tm_wday as WORD;
sys.wMonth = (tm.tm_mon + 1) as WORD;
sys.wYear = (tm.tm_year + 1900) as WORD;
sys
}
fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) {
tm.tm_sec = sys.wSecond as i32;
tm.tm_min = sys.wMinute as i32;
tm.tm_hour = sys.wHour as i32;
tm.tm_mday = sys.wDay as i32;
tm.tm_wday = sys.wDayOfWeek as i32;
tm.tm_mon = (sys.wMonth - 1) as i32;
tm.tm_year = (sys.wYear - 1900) as i32;
tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
fn yday(year: i32, month: i32, day: i32) -> i32 {
let leap = if month > 2 {
if year % 4 == 0 {
1
} else {
2
}
} else {
0
};
let july = if month > 7 { 1 } else { 0 };
(month - 1) * 30 + month / 2 + (day - 1) - leap + july
}
}
macro_rules! call {
($name:ident($($arg:expr),*)) => {
if $name($($arg),*) == 0 {
panic!(concat!(stringify!($name), " failed with: {}"),
io::Error::last_os_error());
}
}
}
pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
let ft = time_to_file_time(sec);
unsafe {
let mut utc = mem::zeroed();
let mut local = mem::zeroed();
call!(FileTimeToSystemTime(&ft, &mut utc));
call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local));
system_time_to_tm(&local, tm);
let local = system_time_to_file_time(&local);
let local_sec = file_time_to_unix_seconds(&local);
let mut tz = mem::zeroed();
GetTimeZoneInformation(&mut tz);
// SystemTimeToTzSpecificLocalTime already applied the biases so
// check if it non standard
tm.tm_utcoff = (local_sec - sec) as i32;
tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 };
}
}
pub fn utc_tm_to_time(tm: &Tm) -> i64 {
unsafe {
let mut ft = mem::zeroed();
let sys_time = tm_to_system_time(tm);
call!(SystemTimeToFileTime(&sys_time, &mut ft));
file_time_to_unix_seconds(&ft)
}
}
pub fn local_tm_to_time(tm: &Tm) -> i64 {
unsafe {
let mut ft = mem::zeroed();
let mut utc = mem::zeroed();
let mut sys_time = tm_to_system_time(tm);
call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc));
call!(SystemTimeToFileTime(&utc, &mut ft));
file_time_to_unix_seconds(&ft)
}
}

1354
third_party/rust/chrono/src/time_delta.rs vendored Normal file

File diff suppressed because it is too large Load Diff

393
third_party/rust/chrono/src/traits.rs vendored Normal file
View File

@@ -0,0 +1,393 @@
use crate::{IsoWeek, Weekday};
/// The common set of methods for date component.
///
/// Methods such as [`year`], [`month`], [`day`] and [`weekday`] can be used to get basic
/// information about the date.
///
/// The `with_*` methods can change the date.
///
/// # Warning
///
/// The `with_*` methods can be convenient to change a single component of a date, but they must be
/// used with some care. Examples to watch out for:
///
/// - [`with_year`] changes the year component of a year-month-day value. Don't use this method if
/// you want the ordinal to stay the same after changing the year, of if you want the week and
/// weekday values to stay the same.
/// - Don't combine two `with_*` methods to change two components of the date. For example to
/// change both the year and month components of a date. This could fail because an intermediate
/// value does not exist, while the final date would be valid.
///
/// For more complex changes to a date, it is best to use the methods on [`NaiveDate`] to create a
/// new value instead of altering an existing date.
///
/// [`year`]: Datelike::year
/// [`month`]: Datelike::month
/// [`day`]: Datelike::day
/// [`weekday`]: Datelike::weekday
/// [`with_year`]: Datelike::with_year
/// [`NaiveDate`]: crate::NaiveDate
pub trait Datelike: Sized {
/// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date).
fn year(&self) -> i32;
/// Returns the absolute year number starting from 1 with a boolean flag,
/// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD).
#[inline]
fn year_ce(&self) -> (bool, u32) {
let year = self.year();
if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) }
}
/// Returns the quarter number starting from 1.
///
/// The return value ranges from 1 to 4.
#[inline]
fn quarter(&self) -> u32 {
(self.month() - 1).div_euclid(3) + 1
}
/// Returns the month number starting from 1.
///
/// The return value ranges from 1 to 12.
fn month(&self) -> u32;
/// Returns the month number starting from 0.
///
/// The return value ranges from 0 to 11.
fn month0(&self) -> u32;
/// Returns the day of month starting from 1.
///
/// The return value ranges from 1 to 31. (The last day of month differs by months.)
fn day(&self) -> u32;
/// Returns the day of month starting from 0.
///
/// The return value ranges from 0 to 30. (The last day of month differs by months.)
fn day0(&self) -> u32;
/// Returns the day of year starting from 1.
///
/// The return value ranges from 1 to 366. (The last day of year differs by years.)
fn ordinal(&self) -> u32;
/// Returns the day of year starting from 0.
///
/// The return value ranges from 0 to 365. (The last day of year differs by years.)
fn ordinal0(&self) -> u32;
/// Returns the day of week.
fn weekday(&self) -> Weekday;
/// Returns the ISO week.
fn iso_week(&self) -> IsoWeek;
/// Makes a new value with the year number changed, while keeping the same month and day.
///
/// This method assumes you want to work on the date as a year-month-day value. Don't use it if
/// you want the ordinal to stay the same after changing the year, of if you want the week and
/// weekday values to stay the same.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (February 29 in a non-leap year).
/// - The year is out of range for [`NaiveDate`].
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
///
/// [`NaiveDate`]: crate::NaiveDate
/// [`DateTime<Tz>`]: crate::DateTime
///
/// # Examples
///
/// ```
/// use chrono::{Datelike, NaiveDate};
///
/// assert_eq!(
/// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(),
/// NaiveDate::from_ymd_opt(2023, 5, 13).unwrap()
/// );
/// // Resulting date 2023-02-29 does not exist:
/// assert!(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().with_year(2023).is_none());
///
/// // Don't use `with_year` if you want the ordinal date to stay the same:
/// assert_ne!(
/// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(),
/// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101
/// );
/// ```
fn with_year(&self, year: i32) -> Option<Self>;
/// Makes a new value with the month number (starting from 1) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (for example `month(4)` when day of the month is 31).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `month` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
///
/// # Examples
///
/// ```
/// use chrono::{Datelike, NaiveDate};
///
/// assert_eq!(
/// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(),
/// NaiveDate::from_ymd_opt(2023, 9, 12).unwrap()
/// );
/// // Resulting date 2023-09-31 does not exist:
/// assert!(NaiveDate::from_ymd_opt(2023, 5, 31).unwrap().with_month(9).is_none());
/// ```
///
/// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist.
/// ```
/// use chrono::{Datelike, NaiveDate};
///
/// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// date.with_year(year)?.with_month(month)
/// }
/// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
/// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value
///
/// // Correct version:
/// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// NaiveDate::from_ymd_opt(year, month, date.day())
/// }
/// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
/// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29));
/// ```
fn with_month(&self, month: u32) -> Option<Self>;
/// Makes a new value with the month number (starting from 0) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (for example `month0(3)` when day of the month is 31).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `month0` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
fn with_month0(&self, month0: u32) -> Option<Self>;
/// Makes a new value with the day of month (starting from 1) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (for example `day(31)` in April).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `day` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
fn with_day(&self, day: u32) -> Option<Self>;
/// Makes a new value with the day of month (starting from 0) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (for example `day0(30)` in April).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `day0` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
fn with_day0(&self, day0: u32) -> Option<Self>;
/// Makes a new value with the day of year (starting from 1) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `ordinal` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
fn with_ordinal(&self, ordinal: u32) -> Option<Self>;
/// Makes a new value with the day of year (starting from 0) changed.
///
/// # Errors
///
/// Returns `None` when:
///
/// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year).
/// - In case of [`DateTime<Tz>`] if the resulting date and time fall within a timezone
/// transition such as from DST to standard time.
/// - The value for `ordinal0` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
fn with_ordinal0(&self, ordinal0: u32) -> Option<Self>;
/// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1.
///
/// # Examples
///
/// ```
/// use chrono::{Datelike, NaiveDate};
///
/// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163);
/// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366);
/// assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1);
/// assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), -365);
/// ```
fn num_days_from_ce(&self) -> i32 {
// See test_num_days_from_ce_against_alternative_impl below for a more straightforward
// implementation.
// we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range.
let mut year = self.year() - 1;
let mut ndays = 0;
if year < 0 {
let excess = 1 + (-year) / 400;
year += excess * 400;
ndays -= excess * 146_097;
}
let div_100 = year / 100;
ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2);
ndays + self.ordinal() as i32
}
}
/// The common set of methods for time component.
pub trait Timelike: Sized {
/// Returns the hour number from 0 to 23.
fn hour(&self) -> u32;
/// Returns the hour number from 1 to 12 with a boolean flag,
/// which is false for AM and true for PM.
#[inline]
fn hour12(&self) -> (bool, u32) {
let hour = self.hour();
let mut hour12 = hour % 12;
if hour12 == 0 {
hour12 = 12;
}
(hour >= 12, hour12)
}
/// Returns the minute number from 0 to 59.
fn minute(&self) -> u32;
/// Returns the second number from 0 to 59.
fn second(&self) -> u32;
/// Returns the number of nanoseconds since the whole non-leap second.
/// The range from 1,000,000,000 to 1,999,999,999 represents
/// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling).
fn nanosecond(&self) -> u32;
/// Makes a new value with the hour number changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_hour(&self, hour: u32) -> Option<Self>;
/// Makes a new value with the minute number changed.
///
/// Returns `None` when the resulting value would be invalid.
fn with_minute(&self, min: u32) -> Option<Self>;
/// Makes a new value with the second number changed.
///
/// Returns `None` when the resulting value would be invalid.
/// As with the [`second`](#tymethod.second) method,
/// the input range is restricted to 0 through 59.
fn with_second(&self, sec: u32) -> Option<Self>;
/// Makes a new value with nanoseconds since the whole non-leap second changed.
///
/// Returns `None` when the resulting value would be invalid.
/// As with the [`nanosecond`](#tymethod.nanosecond) method,
/// the input range can exceed 1,000,000,000 for leap seconds.
fn with_nanosecond(&self, nano: u32) -> Option<Self>;
/// Returns the number of non-leap seconds past the last midnight.
///
/// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399.
///
/// This method is not intended to provide the real number of seconds since midnight on a given
/// day. It does not take things like DST transitions into account.
#[inline]
fn num_seconds_from_midnight(&self) -> u32 {
self.hour() * 3600 + self.minute() * 60 + self.second()
}
}
#[cfg(test)]
mod tests {
use super::Datelike;
use crate::{Days, NaiveDate};
/// Tests `Datelike::num_days_from_ce` against an alternative implementation.
///
/// The alternative implementation is not as short as the current one but it is simpler to
/// understand, with less unexplained magic constants.
#[test]
fn test_num_days_from_ce_against_alternative_impl() {
/// Returns the number of multiples of `div` in the range `start..end`.
///
/// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the
/// behaviour is defined by the following equation:
/// `in_between(start, end, div) == - in_between(end, start, div)`.
///
/// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`.
///
/// # Panics
///
/// Panics if `div` is not positive.
fn in_between(start: i32, end: i32, div: i32) -> i32 {
assert!(div > 0, "in_between: nonpositive div = {}", div);
let start = (start.div_euclid(div), start.rem_euclid(div));
let end = (end.div_euclid(div), end.rem_euclid(div));
// The lowest multiple of `div` greater than or equal to `start`, divided.
let start = start.0 + (start.1 != 0) as i32;
// The lowest multiple of `div` greater than or equal to `end`, divided.
let end = end.0 + (end.1 != 0) as i32;
end - start
}
/// Alternative implementation to `Datelike::num_days_from_ce`
fn num_days_from_ce<Date: Datelike>(date: &Date) -> i32 {
let year = date.year();
let diff = move |div| in_between(1, year, div);
// 365 days a year, one more in leap years. In the gregorian calendar, leap years are all
// the multiples of 4 except multiples of 100 but including multiples of 400.
date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400)
}
for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
assert_eq!(
jan1_year.num_days_from_ce(),
num_days_from_ce(&jan1_year),
"on {:?}",
jan1_year
);
let mid_year = jan1_year + Days::new(133);
assert_eq!(
mid_year.num_days_from_ce(),
num_days_from_ce(&mid_year),
"on {:?}",
mid_year
);
}
}
}

408
third_party/rust/chrono/src/weekday.rs vendored Normal file
View File

@@ -0,0 +1,408 @@
use core::fmt;
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use crate::OutOfRange;
/// The day of week.
///
/// The order of the days of week depends on the context.
/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.)
/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result.
///
/// # Example
/// ```
/// use chrono::Weekday;
///
/// let monday = "Monday".parse::<Weekday>().unwrap();
/// assert_eq!(monday, Weekday::Mon);
///
/// let sunday = Weekday::try_from(6).unwrap();
/// assert_eq!(sunday, Weekday::Sun);
///
/// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0
/// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1
/// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0
/// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1
///
/// assert_eq!(sunday.succ(), monday);
/// assert_eq!(sunday.pred(), Weekday::Sat);
/// ```
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
#[cfg_attr(
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
archive(compare(PartialEq)),
archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
pub enum Weekday {
/// Monday.
Mon = 0,
/// Tuesday.
Tue = 1,
/// Wednesday.
Wed = 2,
/// Thursday.
Thu = 3,
/// Friday.
Fri = 4,
/// Saturday.
Sat = 5,
/// Sunday.
Sun = 6,
}
impl Weekday {
/// The next day in the week.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon`
#[inline]
#[must_use]
pub const fn succ(&self) -> Weekday {
match *self {
Weekday::Mon => Weekday::Tue,
Weekday::Tue => Weekday::Wed,
Weekday::Wed => Weekday::Thu,
Weekday::Thu => Weekday::Fri,
Weekday::Fri => Weekday::Sat,
Weekday::Sat => Weekday::Sun,
Weekday::Sun => Weekday::Mon,
}
}
/// The previous day in the week.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat`
#[inline]
#[must_use]
pub const fn pred(&self) -> Weekday {
match *self {
Weekday::Mon => Weekday::Sun,
Weekday::Tue => Weekday::Mon,
Weekday::Wed => Weekday::Tue,
Weekday::Thu => Weekday::Wed,
Weekday::Fri => Weekday::Thu,
Weekday::Sat => Weekday::Fri,
Weekday::Sun => Weekday::Sat,
}
}
/// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number)
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7
#[inline]
pub const fn number_from_monday(&self) -> u32 {
self.days_since(Weekday::Mon) + 1
}
/// Returns a day-of-week number starting from Sunday = 1.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1
#[inline]
pub const fn number_from_sunday(&self) -> u32 {
self.days_since(Weekday::Sun) + 1
}
/// Returns a day-of-week number starting from Monday = 0.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6
///
/// # Example
///
/// ```
/// # #[cfg(feature = "clock")] {
/// # use chrono::{Local, Datelike};
/// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays.
/// // Use `num_days_from_monday` to index into the array.
/// const MTWRFSU: [char; 7] = ['M', 'T', 'W', 'R', 'F', 'S', 'U'];
///
/// let today = Local::now().weekday();
/// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]);
/// # }
/// ```
#[inline]
pub const fn num_days_from_monday(&self) -> u32 {
self.days_since(Weekday::Mon)
}
/// Returns a day-of-week number starting from Sunday = 0.
///
/// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0
#[inline]
pub const fn num_days_from_sunday(&self) -> u32 {
self.days_since(Weekday::Sun)
}
/// The number of days since the given day.
///
/// # Examples
///
/// ```
/// use chrono::Weekday::*;
/// assert_eq!(Mon.days_since(Mon), 0);
/// assert_eq!(Sun.days_since(Tue), 5);
/// assert_eq!(Wed.days_since(Sun), 3);
/// ```
pub const fn days_since(&self, other: Weekday) -> u32 {
let lhs = *self as u32;
let rhs = other as u32;
if lhs < rhs { 7 + lhs - rhs } else { lhs - rhs }
}
}
impl fmt::Display for Weekday {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(match *self {
Weekday::Mon => "Mon",
Weekday::Tue => "Tue",
Weekday::Wed => "Wed",
Weekday::Thu => "Thu",
Weekday::Fri => "Fri",
Weekday::Sat => "Sat",
Weekday::Sun => "Sun",
})
}
}
/// Any weekday can be represented as an integer from 0 to 6, which equals to
/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
/// Do not heavily depend on this though; use explicit methods whenever possible.
impl TryFrom<u8> for Weekday {
type Error = OutOfRange;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Weekday::Mon),
1 => Ok(Weekday::Tue),
2 => Ok(Weekday::Wed),
3 => Ok(Weekday::Thu),
4 => Ok(Weekday::Fri),
5 => Ok(Weekday::Sat),
6 => Ok(Weekday::Sun),
_ => Err(OutOfRange::new()),
}
}
}
/// Any weekday can be represented as an integer from 0 to 6, which equals to
/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
/// Do not heavily depend on this though; use explicit methods whenever possible.
impl num_traits::FromPrimitive for Weekday {
#[inline]
fn from_i64(n: i64) -> Option<Weekday> {
match n {
0 => Some(Weekday::Mon),
1 => Some(Weekday::Tue),
2 => Some(Weekday::Wed),
3 => Some(Weekday::Thu),
4 => Some(Weekday::Fri),
5 => Some(Weekday::Sat),
6 => Some(Weekday::Sun),
_ => None,
}
}
#[inline]
fn from_u64(n: u64) -> Option<Weekday> {
match n {
0 => Some(Weekday::Mon),
1 => Some(Weekday::Tue),
2 => Some(Weekday::Wed),
3 => Some(Weekday::Thu),
4 => Some(Weekday::Fri),
5 => Some(Weekday::Sat),
6 => Some(Weekday::Sun),
_ => None,
}
}
}
/// An error resulting from reading `Weekday` value with `FromStr`.
#[derive(Clone, PartialEq, Eq)]
pub struct ParseWeekdayError {
pub(crate) _dummy: (),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseWeekdayError {}
impl fmt::Display for ParseWeekdayError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!("{:?}", self))
}
}
impl fmt::Debug for ParseWeekdayError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ParseWeekdayError {{ .. }}")
}
}
// the actual `FromStr` implementation is in the `format` module to leverage the existing code
#[cfg(feature = "serde")]
mod weekday_serde {
use super::Weekday;
use core::fmt;
use serde::{de, ser};
impl ser::Serialize for Weekday {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(&self)
}
}
struct WeekdayVisitor;
impl de::Visitor<'_> for WeekdayVisitor {
type Value = Weekday;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Weekday")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(|_| E::custom("short or long weekday names expected"))
}
}
impl<'de> de::Deserialize<'de> for Weekday {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(WeekdayVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::Weekday;
#[test]
fn test_days_since() {
for i in 0..7 {
let base_day = Weekday::try_from(i).unwrap();
assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon));
assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun));
assert_eq!(base_day.days_since(base_day), 0);
assert_eq!(base_day.days_since(base_day.pred()), 1);
assert_eq!(base_day.days_since(base_day.pred().pred()), 2);
assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3);
assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4);
assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5);
assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6);
assert_eq!(base_day.days_since(base_day.succ()), 6);
assert_eq!(base_day.days_since(base_day.succ().succ()), 5);
assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4);
assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3);
assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2);
assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1);
}
}
#[test]
fn test_formatting_alignment() {
// No exhaustive testing here as we just delegate the
// implementation to Formatter::pad. Just some basic smoke
// testing to ensure that it's in fact being done.
assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon");
assert_eq!(format!("{:^7}", Weekday::Mon), " Mon ");
assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ");
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_serialize() {
use Weekday::*;
use serde_json::to_string;
let cases: Vec<(Weekday, &str)> = vec![
(Mon, "\"Mon\""),
(Tue, "\"Tue\""),
(Wed, "\"Wed\""),
(Thu, "\"Thu\""),
(Fri, "\"Fri\""),
(Sat, "\"Sat\""),
(Sun, "\"Sun\""),
];
for (weekday, expected_str) in cases {
let string = to_string(&weekday).unwrap();
assert_eq!(string, expected_str);
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_deserialize() {
use Weekday::*;
use serde_json::from_str;
let cases: Vec<(&str, Weekday)> = vec![
("\"mon\"", Mon),
("\"MONDAY\"", Mon),
("\"MonDay\"", Mon),
("\"mOn\"", Mon),
("\"tue\"", Tue),
("\"tuesday\"", Tue),
("\"wed\"", Wed),
("\"wednesday\"", Wed),
("\"thu\"", Thu),
("\"thursday\"", Thu),
("\"fri\"", Fri),
("\"friday\"", Fri),
("\"sat\"", Sat),
("\"saturday\"", Sat),
("\"sun\"", Sun),
("\"sunday\"", Sun),
];
for (str, expected_weekday) in cases {
let weekday = from_str::<Weekday>(str).unwrap();
assert_eq!(weekday, expected_weekday);
}
let errors: Vec<&str> =
vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""];
for str in errors {
from_str::<Weekday>(str).unwrap_err();
}
}
#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
let mon = Weekday::Mon;
let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap();
assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon);
}
}

View File

@@ -0,0 +1,165 @@
#![cfg(all(unix, feature = "clock", feature = "std"))]
use std::{path, process, thread};
#[cfg(target_os = "linux")]
use chrono::Days;
use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike};
fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) {
let output = process::Command::new(path)
.arg("-d")
.arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour()))
.arg("+%Y-%m-%d %H:%M:%S %:z")
.output()
.unwrap();
let date_command_str = String::from_utf8(output.stdout).unwrap();
// The below would be preferred. At this stage neither earliest() or latest()
// seems to be consistent with the output of the `date` command, so we simply
// compare both.
// let local = Local
// .with_ymd_and_hms(year, month, day, hour, 5, 1)
// // looks like the "date" command always returns a given time when it is ambiguous
// .earliest();
// if let Some(local) = local {
// assert_eq!(format!("{}\n", local), date_command_str);
// } else {
// // we are in a "Spring forward gap" due to DST, and so date also returns ""
// assert_eq!("", date_command_str);
// }
// This is used while a decision is made whether the `date` output needs to
// be exactly matched, or whether MappedLocalTime::Ambiguous should be handled
// differently
let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap();
match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) {
chrono::MappedLocalTime::Ambiguous(a, b) => assert!(
format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str
),
chrono::MappedLocalTime::Single(a) => {
assert_eq!(format!("{}\n", a), date_command_str);
}
chrono::MappedLocalTime::None => {
assert_eq!("", date_command_str);
}
}
}
/// path to Unix `date` command. Should work on most Linux and Unixes. Not the
/// path for MacOS (/bin/date) which uses a different version of `date` with
/// different arguments (so it won't run which is okay).
/// for testing only
#[allow(dead_code)]
#[cfg(not(target_os = "aix"))]
const DATE_PATH: &str = "/usr/bin/date";
#[allow(dead_code)]
#[cfg(target_os = "aix")]
const DATE_PATH: &str = "/opt/freeware/bin/date";
#[cfg(test)]
/// test helper to sanity check the date command behaves as expected
/// asserts the command succeeded
fn assert_run_date_version() {
// note environment variable `LANG`
match std::env::var_os("LANG") {
Some(lang) => eprintln!("LANG: {:?}", lang),
None => eprintln!("LANG not set"),
}
let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
let stderr = String::from_utf8(out.stderr).unwrap();
// note the `date` binary version
eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr);
assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH);
}
#[test]
fn try_verify_against_date_command() {
if !path::Path::new(DATE_PATH).exists() {
eprintln!("date command {:?} not found, skipping", DATE_PATH);
return;
}
assert_run_date_version();
eprintln!(
"Run command {:?} for every hour from 1975 to 2077, skipping some years...",
DATE_PATH,
);
let mut children = vec![];
for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() {
children.push(thread::spawn(|| {
let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN);
let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN);
while date <= end {
verify_against_date_command_local(DATE_PATH, date);
date += chrono::TimeDelta::try_hours(1).unwrap();
}
}));
}
for child in children {
// Wait for the thread to finish. Returns a result.
let _ = child.join();
}
}
#[cfg(target_os = "linux")]
fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) {
let required_format =
"d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z";
// a%a - depends from localization
// A%A - depends from localization
// b%b - depends from localization
// B%B - depends from localization
// h%h - depends from localization
// c%c - depends from localization
// p%p - depends from localization
// r%r - depends from localization
// x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D
// Z%Z - too many ways to represent it, will most likely fail
let output = process::Command::new(path)
.env("LANG", "c")
.env("LC_ALL", "c")
.arg("-d")
.arg(format!(
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
))
.arg(format!("+{}", required_format))
.output()
.unwrap();
let date_command_str = String::from_utf8(output.stdout).unwrap();
let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap();
let ldt = Local
.from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap())
.unwrap();
let formatted_date = format!("{}\n", ldt.format(required_format));
assert_eq!(date_command_str, formatted_date);
}
#[test]
#[cfg(target_os = "linux")]
fn try_verify_against_date_command_format() {
if !path::Path::new(DATE_PATH).exists() {
eprintln!("date command {:?} not found, skipping", DATE_PATH);
return;
}
assert_run_date_version();
let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap();
while date.year() < 2008 {
verify_against_date_command_format_local(DATE_PATH, date);
date = date + Days::new(55);
}
}

View File

@@ -1,67 +1,89 @@
#[cfg(all(test, feature = "wasmbind"))]
mod test {
extern crate chrono;
extern crate wasm_bindgen_test;
//! Run this test with:
//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind`
//!
//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with
//! the host system.
//! The check will fail if the local timezone does not match one of the timezones defined below.
use self::chrono::prelude::*;
use self::wasm_bindgen_test::*;
#![cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
feature = "clock",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
#[wasm_bindgen_test]
fn now() {
let utc: DateTime<Utc> = Utc::now();
let local: DateTime<Local> = Local::now();
use chrono::prelude::*;
use wasm_bindgen_test::*;
// Ensure time set by the test script is correct
let now = env!("NOW");
let actual = Utc.datetime_from_str(&now, "%s").unwrap();
let diff = utc - actual;
assert!(
diff < chrono::Duration::minutes(5),
"expected {} - {} == {} < 5m (env var: {})",
utc,
actual,
diff,
now,
);
#[wasm_bindgen_test]
fn now() {
let utc: DateTime<Utc> = Utc::now();
let local: DateTime<Local> = Local::now();
let tz = env!("TZ");
eprintln!("testing with tz={}", tz);
// Ensure time set by the test script is correct
let now = env!("NOW");
let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc();
let diff = utc - actual;
assert!(
diff < chrono::TimeDelta::try_minutes(5).unwrap(),
"expected {} - {} == {} < 5m (env var: {})",
utc,
actual,
diff,
now,
);
// Ensure offset retrieved when getting local time is correct
let expected_offset = match tz {
"ACST-9:30" => FixedOffset::east(19 * 30 * 60),
"Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully
"EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60),
"EST" | "-0500" => FixedOffset::east(-5 * 60 * 60),
"UTC0" | "+0000" => FixedOffset::east(0),
tz => panic!("unexpected TZ {}", tz),
};
assert_eq!(
&expected_offset,
local.offset(),
"expected: {:?} local: {:?}",
expected_offset,
local.offset(),
);
}
let tz = env!("TZ");
eprintln!("testing with tz={}", tz);
#[wasm_bindgen_test]
fn from_is_exact() {
let now = js_sys::Date::new_0();
let dt = DateTime::<Utc>::from(now.clone());
assert_eq!(now.get_time() as i64, dt.timestamp_millis());
}
#[wasm_bindgen_test]
fn local_from_local_datetime() {
let now = Local::now();
let ndt = now.naive_local();
let res = match Local.from_local_datetime(&ndt).single() {
Some(v) => v,
None => panic! {"Required for test!"},
};
assert_eq!(now, res);
}
// 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());
}
#[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(), 11); // months are numbered 0..=11
assert_eq!(js_date.get_utc_date(), 1);
assert_eq!(js_date.get_utc_hours(), 3);
assert_eq!(js_date.get_utc_minutes(), 1);
assert_eq!(js_date.get_utc_seconds(), 55);
assert_eq!(js_date.get_utc_milliseconds(), 974);
}

View File

@@ -0,0 +1,28 @@
#![cfg(all(windows, feature = "clock", feature = "std"))]
use std::fs;
use windows_bindgen::bindgen;
#[test]
fn gen_bindings() {
let input = "src/offset/local/win_bindings.txt";
let output = "src/offset/local/win_bindings.rs";
let existing = fs::read_to_string(output).unwrap();
bindgen(["--no-deps", "--etc", input]);
// Check the output is the same as before.
// Depending on the git configuration the file may have been checked out with `\r\n` newlines or
// with `\n`. Compare line-by-line to ignore this difference.
let mut new = fs::read_to_string(output).unwrap();
if existing.contains("\r\n") && !new.contains("\r\n") {
new = new.replace("\n", "\r\n");
} else if !existing.contains("\r\n") && new.contains("\r\n") {
new = new.replace("\r\n", "\n");
}
similar_asserts::assert_eq!(existing, new);
if !new.lines().eq(existing.lines()) {
panic!("generated file `{}` is changed.", output);
}
}

View File

@@ -0,0 +1 @@
{"files":{"Cargo.toml":"8f06eca1c0e108d0422687eeb87030520ae7bd956efc92352599cc9cf079d9a3","LICENSE-APACHE":"696759d65dfe558ff7d9f031c76db19ec5c0767470fb67c4e8d990820d1e99c9","LICENSE-MIT":"da28ccc6b158fc2d8cccc74e99794b1cff1d29bd7bbeb019442fcf0c04c6cad9","README.md":"5b1ad9309b716374cc1bdcd025f525fac31b2f413e6c4d311e207fa6b1f96a83","build.rs":"10304831100a60c1c2b990762dcfeb47dae8342cf9b54595bec94884e7de5784","src/implementation.cc":"66d2ecfe58ec543e27a6fb3a96526a07cd1ac43a2370344f856529e5a112ce0f","src/lib.rs":"e58db019554bd372f0a187f8f51f96624cdf21bcef507de2093e1d49ca0787cd"},"package":"f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"}

View File

@@ -0,0 +1,34 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "iana-time-zone-haiku"
version = "0.1.2"
authors = ["René Kijewski <crates.io@k6i.de>"]
description = "iana-time-zone support crate for Haiku OS"
readme = "README.md"
keywords = [
"IANA",
"time",
]
categories = [
"date-and-time",
"internationalization",
"os",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/strawlab/iana-time-zone"
[dependencies]
[build-dependencies.cc]
version = "1.0.79"

View File

@@ -0,0 +1,201 @@
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 2020 Andrew Straw
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,25 @@
Copyright (c) 2020 Andrew D. Straw
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.

View File

@@ -0,0 +1,8 @@
# iana-time-zone-haiku
[![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku)
[![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/)
[![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku)
[![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=main)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain)
[iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS.

View File

@@ -0,0 +1,22 @@
use std::env;
fn main() {
cc::Build::new()
.warnings(false)
.cpp(true)
.file("src/implementation.cc")
.flag_if_supported("-std=c++11")
.compile("tz_haiku");
println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=src/implementation.cc");
println!("cargo:rerun-if-changed=src/interface.h");
let target = env::var_os("TARGET").expect("cargo should set TARGET env var");
let target = target
.to_str()
.expect("TARGET env var should be valid UTF-8");
if target.contains("haiku") {
println!("cargo:rustc-link-lib=be");
}
}

View File

@@ -0,0 +1,67 @@
#include <cstddef>
#ifdef __HAIKU__
#include <cstring>
#include <Errors.h>
#include <LocaleRoster.h>
#include <String.h>
#include <TimeZone.h>
extern "C" {
size_t iana_time_zone_haiku_get_tz(char *buf, size_t buf_size) {
try {
static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size");
if (buf_size == 0) {
return 0;
}
// `BLocaleRoster::Default()` returns a reference to a statically allocated object.
// https://github.com/haiku/haiku/blob/8f16317/src/kits/locale/LocaleRoster.cpp#L143-L147
BLocaleRoster *locale_roster(BLocaleRoster::Default());
if (!locale_roster) {
return 0;
}
BTimeZone tz(NULL, NULL);
if (locale_roster->GetDefaultTimeZone(&tz) != B_OK) {
return 0;
}
BString bname(tz.ID());
int32_t ilength(bname.Length());
if (ilength <= 0) {
return 0;
}
size_t length(ilength);
if (length > buf_size) {
return 0;
}
// BString::String() returns a borrowed string.
// https://www.haiku-os.org/docs/api/classBString.html#ae4fe78b06c8e3310093b80305e14ba87
const char *sname(bname.String());
if (!sname) {
return 0;
}
std::memcpy(buf, sname, length);
return length;
} catch (...) {
return 0;
}
}
} // extern "C"
#else
extern "C" {
size_t iana_time_zone_haiku_get_tz(char *buf, size_t buf_size) { return 0; }
} // extern "C"
#endif

View File

@@ -0,0 +1,71 @@
#![warn(clippy::all)]
#![warn(clippy::cargo)]
#![warn(clippy::undocumented_unsafe_blocks)]
#![allow(unknown_lints)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unsafe_op_in_unsafe_fn)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
//! # iana-time-zone-haiku
//!
//! [![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku)
//! [![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/)
//! [![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku)
//! [![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=main)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain)
//!
//! [iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS.
use std::os::raw::c_char;
extern "C" {
fn iana_time_zone_haiku_get_tz(buf: *mut c_char, buf_size: usize) -> usize;
}
/// Get the current IANA time zone as a string.
///
/// On Haiku platforms this function will return [`Some`] with the timezone string
/// or [`None`] if an error occurs. On all other platforms, [`None`] is returned.
///
/// # Examples
///
/// ```
/// let timezone = iana_time_zone_haiku::get_timezone();
/// ```
#[must_use]
pub fn get_timezone() -> Option<String> {
// The longest name in the IANA time zone database is 25 ASCII characters long.
let mut buf = [0u8; 32];
// SAFETY: a valid, aligned, non-NULL pointer and length are given which
// point to a single allocation.
let len = unsafe {
let buf_size = buf.len();
let buf_ptr = buf.as_mut_ptr().cast::<c_char>();
iana_time_zone_haiku_get_tz(buf_ptr, buf_size)
};
// The name should not be empty, or excessively long.
match buf.get(..len)? {
b"" => None,
s => Some(std::str::from_utf8(s).ok()?.to_owned()),
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(not(target_os = "haiku"))]
fn test_fallback_on_non_haiku_platforms() {
assert!(super::get_timezone().is_none());
}
#[test]
#[cfg(target_os = "haiku")]
fn test_retrieve_time_zone_on_haiku_platforms() {
let timezone = super::get_timezone().unwrap();
assert!(!timezone.is_empty());
}
}

View File

@@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"467a75b20956c8201acdad52b44b1cd146913a4df9f5282e47bf144dea2878da","Cargo.lock":"0cd9cc06007ba8411bb0c1202ac7a587b261aa355287f54737ca0d5c0554f160","Cargo.toml":"b5fcc4b21e07e9f085a5ed540134e01f107e0af2e9358e3cb49aaebb2d70bfae","LICENSE-APACHE":"696759d65dfe558ff7d9f031c76db19ec5c0767470fb67c4e8d990820d1e99c9","LICENSE-MIT":"da28ccc6b158fc2d8cccc74e99794b1cff1d29bd7bbeb019442fcf0c04c6cad9","README.md":"22f9a823adec27aca10eb6dfb5e4e1e9d485e1d5f78348f788bfc81dbee2787f","bindings.txt":"6e9b2e58051bf22fb39b38dc04e54bf75b6204fbd97df06bc58617b1b5d14461","deny.toml":"bde861b1ca6304017f8a57268058aa99ad33d89b1498bba68086e65cca7fe7a2","examples/get_timezone.rs":"c4db7b1cc71c7b3728ddd70e76c0dbd40239c6b1b8c705cb63476757d3177dec","examples/get_timezone_loop.rs":"5e9da42eabd529f5f8d04acc5c1eb84575b9ed38a9226e76e91a1717089b016e","examples/stress-test.rs":"3ad469de5a650389699c9ffe5fd78af2bdd46e7140cc05391c60d793fc6f8e98","src/ffi_utils.rs":"4a49637f60e4d2ab1afecb168e884cee8137a32037d440f9f032196c9fd6f159","src/lib.rs":"0982d7d2e2228b3b4d6a62a4dd5e28ea22086074edce0f0b07ce72fefccacdbf","src/platform.rs":"7847381ca6976d9f39ce84759a3b0d7aabe31eae7a88bc20d7fb99bc376782b0","src/tz_aix.rs":"86fd048ff14062c53d1aa770ee715961f7b1099337453ca2eb297a74fc59f673","src/tz_android.rs":"3da37f1b87f87a0ed215734f2b373b2d187bcf49386adfe8bfff207f9a5e8fe2","src/tz_darwin.rs":"8938e34f1f7c4cf813deb8d008ea6ee130dcb30ada6db3d068c3f0990f81165b","src/tz_freebsd.rs":"4247af5c6dd0712705186ed54d789193c64139f707af316d4fde86aa1e2a1b13","src/tz_haiku.rs":"761afd80301683a44bf937bbf6b13c5c792af42ed7037623bbeccbab6d0aa8fc","src/tz_illumos.rs":"375ae951d1417f63e6d77c9add7f7f646f24c0054cb8407bd4b9f06907494888","src/tz_linux.rs":"fc62ae0275e745c4fc6552c25f70812e37e91d78a16d9a40e5688331a7af04ed","src/tz_netbsd.rs":"ec278bbe1cb5f648c063ec23bff6081146454b9f6aa3918b9ca50b8804d5838f","src/tz_ohos.rs":"f1db259b38f6890ec3c7b357e15d10f2aa166dc84394bac58fa62cb31d4559dc","src/tz_wasm32_unknown.rs":"10aa33caa86645a16e2126fe1a86dda2b57f63caa9addcd726245fbc9657dc1b","src/tz_windows.rs":"804c11a3c638c93626459612cd444df64e9962668d1e8cd6f0774301c00c9529","src/windows_bindings.rs":"d58d11d01f5f5335febb91e83f32956801824553e2b26216bfd16e6a0c87803c"},"package":"b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"}

View File

@@ -0,0 +1,353 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.63] - 2025-03-31
### Changes
- Bump MSRV (minimum supported rust version) to 1.62 ([#131](https://github.com/strawlab/iana-time-zone/pull/131))
- Bump `windows-core` to `0.56-0.61` range ([#131](https://github.com/strawlab/iana-time-zone/pull/131), [#133](https://github.com/strawlab/iana-time-zone/pull/133))
## [0.1.62] - 2025-03-24
### Changed
- Bump MSRV (minimum supported rust version) to 1.61 ([#157](https://github.com/strawlab/iana-time-zone/pull/157))
- Update to rust edition 2021 ([#161](https://github.com/strawlab/iana-time-zone/pull/161))
- Address high and medium severity zizmor findings ([#163](https://github.com/strawlab/iana-time-zone/pull/163))
### Added
- Added support for tvOS, watchOS and visionOS ([#146](https://github.com/strawlab/iana-time-zone/pull/146)).
- implement OpenHarmony support ([#150](https://github.com/strawlab/iana-time-zone/pull/150))
## [0.1.61] - 2024-09-16
### Changed
- Depend on wasm-bindgen 0.2.89 or higher ([#134](https://github.com/strawlab/iana-time-zone/pull/134))
- Do not use wasm_bindgen in wasm32-unknown-emscripten environment ([#130](https://github.com/strawlab/iana-time-zone/pull/130))
## [0.1.60] - 2024-02-03
### Changed
- correct `windows-core` dependency version ([#127](https://github.com/strawlab/iana-time-zone/pull/127))
## [0.1.59] - 2023-12-30
### Changed
- update `windows` dependency ([#125](https://github.com/strawlab/iana-time-zone/pull/125))
## [0.1.58] - 2023-10-17
### Added
- use windows-core with embedded bindings via windows-bindgen ([#117](https://github.com/strawlab/iana-time-zone/pull/117))
- implement GNU Hurd support ([#121](https://github.com/strawlab/iana-time-zone/pull/121))
- implement AIX support ([#57](https://github.com/strawlab/iana-time-zone/pull/57))
## [0.1.57] - 2023-06-07
### Added
- implement OpenWRT support ([#109](https://github.com/strawlab/iana-time-zone/pull/109))
## [0.1.56] - 2023-04-03
### Changed
- update `windows` dependency ([#102](https://github.com/strawlab/iana-time-zone/pull/102))
## [0.1.55] - 2023-03-30
### Changed
- update `windows` dependency ([#101](https://github.com/strawlab/iana-time-zone/pull/101))
## [0.1.54] - 2023-03-21
### Changed
- replace `winapi` dependency with `windows` ([#97](https://github.com/strawlab/iana-time-zone/pull/97))
- bump msrv to 1.48 ([#91](https://github.com/strawlab/iana-time-zone/pull/91))
## [0.1.53] - 2022-10-28
### Fixed
- remove lint causing breakage on rust 1.45-1.51 ([#84](https://github.com/strawlab/iana-time-zone/pull/84))
## [0.1.52] - 2022-10-28
### Fixed
- fix for NixOS ([#81](https://github.com/strawlab/iana-time-zone/pull/81))
### Changed
- allow building the haiku crate on other hosts([#75](https://github.com/strawlab/iana-time-zone/pull/75))
- various improvements in continuous integration and source quality
([#76](https://github.com/strawlab/iana-time-zone/pull/76)),
([#77](https://github.com/strawlab/iana-time-zone/pull/77)),
([#78](https://github.com/strawlab/iana-time-zone/pull/78)),
([#81](https://github.com/strawlab/iana-time-zone/pull/81))
## [0.1.51] - 2022-10-08
### Changed
- bump MSRV to 1.38 ([#70](https://github.com/strawlab/iana-time-zone/pull/70))
- Refactor Android property key CStr construction to add tests ([#69](https://github.com/strawlab/iana-time-zone/pull/69))
- Refactor MacOS implementation a lot ([#67](https://github.com/strawlab/iana-time-zone/pull/67))
### Added
- Implement for Haiku ([#66](https://github.com/strawlab/iana-time-zone/pull/66))
### Fixed
- Fix spelling of 'initialized' in sync::Once statics ([#63](https://github.com/strawlab/iana-time-zone/pull/63))
## [0.1.50] - 2022-09-23
### Fixed
- Reduce MSRV for Android again ([#62](https://github.com/strawlab/iana-time-zone/pull/62))
## [0.1.49] - 2022-09-22
### Changed
- `once_cell` dependency is not needed ([#61](https://github.com/strawlab/iana-time-zone/pull/61))
## [0.1.48] - 2022-09-12
### Changed
- Downgrade requirements for WASM dependencies ([#58](https://github.com/strawlab/iana-time-zone/pull/58))
- Reduce MSRV for Tier 1 platforms to 1.31 ([#59](https://github.com/strawlab/iana-time-zone/pull/59))
## [0.1.47] - 2022-08-30
### Changed
- Update `android_system_properties` to v0.1.5 to run 9786% faster (YMMV) ([#56](https://github.com/strawlab/iana-time-zone/pull/56))
## [0.1.46] - 2022-08-18
### Added
- Implement for Solaris ([#55](https://github.com/strawlab/iana-time-zone/pull/55))
## [0.1.45] - 2022-08-16
### Fixed
- Fix potential use after free in MacOS / iOS ([#54](https://github.com/strawlab/iana-time-zone/pull/54), [RUSTSEC-2022-0049](https://rustsec.org/advisories/RUSTSEC-2022-0049.html))
- Fix typos in README ([#53](https://github.com/strawlab/iana-time-zone/pull/53))
## [0.1.44] - 2022-08-11
### Fixed
- "/etc/localtime" may be relative link ([#49](https://github.com/strawlab/iana-time-zone/pull/49))
## [0.1.43] - 2022-08-11
### Changed
- Use `core-foundation-sys` instead of `core-foundation` ([#50](https://github.com/strawlab/iana-time-zone/pull/50))
## [0.1.42] - 2022-08-10
### Fixed
- Fix implementation for Redhat based distros ([#48](https://github.com/strawlab/iana-time-zone/pull/48))
## [0.1.41] - 2022-08-02
### Added
- Add `fallback` feature ([#46](https://github.com/strawlab/iana-time-zone/pull/46))
## [0.1.40] - 2022-07-29
### Added
- Implement for Android ([#45](https://github.com/strawlab/iana-time-zone/pull/45))
## [0.1.38] - 2022-07-27
### Added
- Implement illumos ([#44](https://github.com/strawlab/iana-time-zone/pull/44))
### Changed
- Update examples in README
## [0.1.37] - 2022-07-23
### Added
- Support iOS ([#41](https://github.com/strawlab/iana-time-zone/pull/41))
### Changed
- Implement `std::err::source()`, format `IoError` ([#42](https://github.com/strawlab/iana-time-zone/pull/42))
## [0.1.36] - 2022-07-21
### Fixed
- Fail to compile for WASI ([#40](https://github.com/strawlab/iana-time-zone/pull/40))
## [0.1.35] - 2022-06-29
### Added
- Implement for FreeBSD, NetBSD, OpenBSD and Dragonfly ([#39](https://github.com/strawlab/iana-time-zone/pull/39))
## [0.1.34] - 2022-06-29
### Added
- Implement for wasm32 ([#38](https://github.com/strawlab/iana-time-zone/pull/38))
## [0.1.33] - 2022-04-15
### Changed
- Use `winapi` crate instead of `windows` crate ([#35](https://github.com/strawlab/iana-time-zone/pull/35))
## [0.1.32] - 2022-04-06
### Changed
- Update `windows` requirement from 0.34 to 0.35 ([#34](https://github.com/strawlab/iana-time-zone/pull/34))
## [0.1.31] - 2022-03-16
### Changed
- Update `windows` requirement from 0.33 to 0.34 ([#33](https://github.com/strawlab/iana-time-zone/pull/33))
## [0.1.30] - 2022-02-28
### Changed
- Fewer string allocations ([#32](https://github.com/strawlab/iana-time-zone/pull/32))
## [0.1.29] - 2022-02-25
### Changed
- Update `windows` requirement from 0.32 to 0.33 ([#31](https://github.com/strawlab/iana-time-zone/pull/31))
## [0.1.28] - 2022-02-04
### Changed
- Update `windows` requirement from 0.30 to 0.32 ([#30](https://github.com/strawlab/iana-time-zone/pull/30))
## [0.1.27] - 2022-01-14
### Changed
- Update `windows` requirement from 0.29 to 0.30 ([#29](https://github.com/strawlab/iana-time-zone/pull/29))
## [0.1.26] - 2021-12-23
### Changed
- Update `windows` requirement from 0.28 to 0.29 ([#28](https://github.com/strawlab/iana-time-zone/pull/28))
## [0.1.25] - 2021-11-18
### Changed
- Update `windows` requirement from 0.27 to 0.28 ([#27](https://github.com/strawlab/iana-time-zone/pull/27))
## [0.1.24] - 2021-11-16
### Changed
- Update `windows` requirement from 0.26 to 0.27 ([#26](https://github.com/strawlab/iana-time-zone/pull/26))
## [0.1.23] - 2021-11-12
### Changed
- Update `windows` requirement from 0.25 to 0.26 ([#25](https://github.com/strawlab/iana-time-zone/pull/25))
## [0.1.22] - 2021-11-08
### Changed
- Update `windows` requirement from 0.24 to 0.25 ([#24](https://github.com/strawlab/iana-time-zone/pull/24))
## [0.1.21] - 2021-11-02
### Changed
- Update `windows` requirement from 0.23 to 0.24 ([#23](https://github.com/strawlab/iana-time-zone/pull/23))
## [0.1.20] - 2021-10-29
### Changed
- Update `windows` requirement from 0.21 to 0.23 ([#22](https://github.com/strawlab/iana-time-zone/pull/22))
## [0.1.19] - 2021-09-27
### Changed
- Update `windows` requirement from 0.19 to 0.21 ([#18](https://github.com/strawlab/iana-time-zone/pull/18), [#20](https://github.com/strawlab/iana-time-zone/pull/20))
- Update `chrono-tz` requirement from 0.5 to 0.6 ([#19](https://github.com/strawlab/iana-time-zone/pull/19))
## [0.1.18] - 2021-08-23
### Changed
- Update `windows` requirement from 0.18 to 0.19 ([#17](https://github.com/strawlab/iana-time-zone/pull/17))
## [0.1.16] - 2021-07-26
### Changed
- Update `windows` requirement from 0.17 to 0.18 ([#16](https://github.com/strawlab/iana-time-zone/pull/16))
## [0.1.15] - 2021-07-08
### Changed
- Update `windows` requirement from 0.14 to 0.17 ([#15](https://github.com/strawlab/iana-time-zone/pull/15))
## [0.1.14] - 2021-07-07
### Changed
- Update `windows` requirement from 0.13 to 0.14 ([#14](https://github.com/strawlab/iana-time-zone/pull/14))
## [0.1.13] - 2021-06-28
### Changed
- Update `windows` requirement from 0.12 to 0.13 ([#13](https://github.com/strawlab/iana-time-zone/pull/13))
## [0.1.12] - 2021-06-28
### Changed
- Update `windows` requirement from 0.11 to 0.12 ([#12](https://github.com/strawlab/iana-time-zone/pull/12))
## [0.1.11] - 2021-06-12
### Changed
- Update `windows` requirement from 0.10 to 0.11 ([#11](https://github.com/strawlab/iana-time-zone/pull/11))
## [0.1.10] - 2021-05-13
### Changed
- Update `windows` requirement from 0.9 to 0.10 ([#10](https://github.com/strawlab/iana-time-zone/pull/10))
## [0.1.9] - 2021-04-28
### Changed
- Update `windows` requirement from 0.8 to 0.9 ([#8](https://github.com/strawlab/iana-time-zone/pull/8))
## [0.1.8] - 2021-04-13
### Changed
- Update `windows` requirement from 0.7 to 0.8 ([#7](https://github.com/strawlab/iana-time-zone/pull/7))
## [0.1.7] - 2021-03-30
### Changed
- Update `windows` requirement from 0.6 to 0.7 ([#6](https://github.com/strawlab/iana-time-zone/pull/6))
## [0.1.6] - 2021-03-24
### Changed
- Update `windows` requirement from 0.5 to 0.6 ([#5](https://github.com/strawlab/iana-time-zone/pull/5))
## [0.1.5] - 2021-03-20
### Changed
- Update `windows` requirement from 0.4 to 0.5 ([#4](https://github.com/strawlab/iana-time-zone/pull/4))
## [0.1.4] - 2021-03-11
### Changed
- Update `windows` requirement from 0.3 to 0.4 ([#3](https://github.com/strawlab/iana-time-zone/pull/3))
## [0.1.3] - 2021-02-22
### Changed
- Use `windows` crate instead of `winrt`
## [0.1.2] - 2020-10-09
### Changed
- Update `core-foundation` requirement from 0.7 to 0.9 ([#1](https://github.com/strawlab/iana-time-zone/pull/1))
## [0.1.1] - 2020-06-27
### Changed
- Update `core-foundation` requirement from 0.5 to 0.7
## [0.1.0] - 2020-06-27
### Added
- Implement for Linux, Windows, MacOS
[0.1.63]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.63
[0.1.62]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.62
[0.1.61]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.61
[0.1.60]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.60
[0.1.59]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.59
[0.1.58]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.58
[0.1.57]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.57
[0.1.56]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.56
[0.1.55]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.55
[0.1.54]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.54
[0.1.53]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.53
[0.1.52]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.52
[0.1.51]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.51
[0.1.50]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.50
[0.1.49]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.49
[0.1.48]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.48
[0.1.47]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.47
[0.1.46]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.46
[0.1.45]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.45
[0.1.44]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.44
[0.1.43]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.43
[0.1.42]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.42
[0.1.41]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.41
[0.1.40]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.40
[0.1.39]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.39
[0.1.38]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.38
[0.1.37]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.37
[0.1.36]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.36
[0.1.35]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.35
[0.1.34]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.34
[0.1.33]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.33
[0.1.32]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.32
[0.1.31]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.31
[0.1.30]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.30
[0.1.29]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.29
[0.1.28]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.28
[0.1.27]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.27
[0.1.26]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.26
[0.1.25]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.25
[0.1.24]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.24
[0.1.23]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.23
[0.1.22]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.22
[0.1.21]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.21
[0.1.20]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.20
[0.1.19]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.19
[0.1.18]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.18
[0.1.17]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.17
[0.1.16]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.16
[0.1.15]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.15
[0.1.14]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.14
[0.1.13]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.13
[0.1.12]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.12
[0.1.11]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.11
[0.1.10]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.10
[0.1.9]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.9
[0.1.8]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.8
[0.1.7]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.7
[0.1.6]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.6
[0.1.5]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.5
[0.1.4]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.4
[0.1.3]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.3
[0.1.2]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.2
[0.1.1]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.1
[0.1.0]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.0

590
third_party/rust/iana-time-zone/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,590 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"num-traits",
]
[[package]]
name = "chrono-tz"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3"
dependencies = [
"chrono",
"chrono-tz-build",
"phf",
]
[[package]]
name = "chrono-tz-build"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402"
dependencies = [
"parse-zoneinfo",
"phf_codegen",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
dependencies = [
"android_system_properties",
"chrono-tz",
"core-foundation-sys",
"getrandom",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"wasm-bindgen-test",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minicov"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
dependencies = [
"cc",
"walkdir",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parse-zoneinfo"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
dependencies = [
"regex",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
dependencies = [
"js-sys",
"minicov",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -0,0 +1,93 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.62.0"
name = "iana-time-zone"
version = "0.1.63"
authors = [
"Andrew Straw <strawman@astraw.com>",
"René Kijewski <rene.kijewski@fu-berlin.de>",
"Ryan Lopopolo <rjl@hyperbo.la>",
]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "get the IANA time zone for the current system"
readme = "README.md"
keywords = [
"IANA",
"time",
]
categories = [
"date-and-time",
"internationalization",
"os",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/strawlab/iana-time-zone"
[features]
fallback = []
[lib]
name = "iana_time_zone"
path = "src/lib.rs"
[[example]]
name = "get_timezone"
path = "examples/get_timezone.rs"
[[example]]
name = "get_timezone_loop"
path = "examples/get_timezone_loop.rs"
[[example]]
name = "stress-test"
path = "examples/stress-test.rs"
[dev-dependencies.chrono-tz]
version = "0.10.1"
[dev-dependencies.getrandom]
version = "0.2.1"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.js-sys]
version = "0.3.66"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.log]
version = "0.4.14"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.wasm-bindgen]
version = "0.2.89"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies.getrandom]
version = "0.2.1"
features = ["js"]
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies.wasm-bindgen-test]
version = "0.3.46"
[target.'cfg(target_os = "android")'.dependencies.android_system_properties]
version = "0.1.5"
[target.'cfg(target_os = "haiku")'.dependencies.iana-time-zone-haiku]
version = "0.1.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-core]
version = ">=0.56, <=0.61"
[target.'cfg(target_vendor = "apple")'.dependencies.core-foundation-sys]
version = "0.8.6"

View File

@@ -0,0 +1,201 @@
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 2020 Andrew Straw
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,25 @@
Copyright (c) 2020 Andrew D. Straw
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.

View File

@@ -0,0 +1,51 @@
# iana-time-zone - get the IANA time zone for the current system
[![Crates.io](https://img.shields.io/crates/v/iana-time-zone.svg)](https://crates.io/crates/iana-time-zone)
[![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/)
[![Crate License](https://img.shields.io/crates/l/iana-time-zone.svg)](https://crates.io/crates/iana-time-zone)
[![build](https://github.com/strawlab/iana-time-zone/actions/workflows/rust.yml/badge.svg)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain)
This small utility crate gets the IANA time zone for the current system. This is
also known as the [tz database], tzdata, the zoneinfo database, and the Olson
database.
[tz database]: https://en.wikipedia.org/wiki/Tz_database
Example:
```rust
// Get the current time zone as a string.
let tz_str = iana_time_zone::get_timezone()?;
println!("The current time zone is: {}", tz_str);
```
You can test this is working on your platform with:
```
cargo run --example get_timezone
```
## Minimum supported rust version policy
This crate has a minimum supported rust version (MSRV) of 1.62.0 for [Tier 1]
platforms.
[tier 1]: https://doc.rust-lang.org/1.62.0/rustc/platform-support.html
Updates to the MSRV are sometimes necessary due to the MSRV of dependencies.
MSRV updates will not be indicated as a breaking change to the semver version.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
<http://opensource.org/licenses/MIT>)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

View File

@@ -0,0 +1,4 @@
--out src/windows_bindings.rs
--filter
Windows.Globalization.Calendar

View File

@@ -0,0 +1,4 @@
[licenses]
version = 2
allow = ["Apache-2.0", "MIT", "Unicode-3.0"]
private = { ignore = true }

View File

@@ -0,0 +1,6 @@
use iana_time_zone::{get_timezone, GetTimezoneError};
fn main() -> Result<(), GetTimezoneError> {
println!("{}", get_timezone()?);
Ok(())
}

View File

@@ -0,0 +1,13 @@
use std::thread;
use std::time::Duration;
use iana_time_zone::{get_timezone, GetTimezoneError};
const WAIT: Duration = Duration::from_secs(1);
fn main() -> Result<(), GetTimezoneError> {
loop {
println!("{}", get_timezone()?);
thread::sleep(WAIT);
}
}

View File

@@ -0,0 +1,25 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::spawn;
use iana_time_zone::get_timezone;
const THREADS: usize = 10;
const ITERATIONS: usize = 100_000;
static COUNT: AtomicUsize = AtomicUsize::new(0);
fn main() {
let mut threads = Vec::with_capacity(THREADS);
for _ in 0..THREADS {
threads.push(spawn(|| {
for _ in 0..ITERATIONS {
get_timezone().unwrap();
COUNT.fetch_add(1, Ordering::Relaxed);
}
}));
}
for thread in threads {
thread.join().unwrap();
}
assert_eq!(COUNT.load(Ordering::SeqCst), THREADS * ITERATIONS);
}

View File

@@ -0,0 +1,718 @@
//! Cross platform FFI helpers.
#[cfg(any(test, target_os = "android"))]
use std::ffi::CStr;
/// A buffer to store the timezone name when calling the C API.
#[cfg(any(test, target_vendor = "apple", target_env = "ohos"))]
pub(crate) mod buffer {
/// The longest name in the IANA time zone database is 32 ASCII characters long.
pub const MAX_LEN: usize = 64;
/// Return a buffer to store the timezone name.
///
/// The buffer is used to store the timezone name when calling the C API.
pub const fn tzname_buf() -> [u8; MAX_LEN] {
[0; MAX_LEN]
}
}
// The system property named 'persist.sys.timezone' contains the name of the
// current timezone.
//
// From https://android.googlesource.com/platform/bionic/+/gingerbread-release/libc/docs/OVERVIEW.TXT#79:
//
// > The name of the current timezone is taken from the TZ environment variable,
// > if defined. Otherwise, the system property named 'persist.sys.timezone' is
// > checked instead.
//
// TODO: Use a `c"..."` literal when MSRV is upgraded beyond 1.77.0.
// https://doc.rust-lang.org/edition-guide/rust-2021/c-string-literals.html
#[cfg(any(test, target_os = "android"))]
const ANDROID_TIMEZONE_PROPERTY_NAME: &[u8] = b"persist.sys.timezone\0";
/// Return a [`CStr`] to access the timezone from an Android system properties
/// environment.
#[cfg(any(test, target_os = "android"))]
pub(crate) fn android_timezone_property_name() -> &'static CStr {
// In tests or debug mode, opt into extra runtime checks.
if cfg!(any(test, debug_assertions)) {
return CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
}
// SAFETY: the key is NUL-terminated and there are no other NULs, this
// invariant is checked in tests.
unsafe { CStr::from_bytes_with_nul_unchecked(ANDROID_TIMEZONE_PROPERTY_NAME) }
}
#[cfg(test)]
mod tests {
use core::mem::size_of_val;
use std::ffi::CStr;
use super::buffer::{tzname_buf, MAX_LEN};
use super::{android_timezone_property_name, ANDROID_TIMEZONE_PROPERTY_NAME};
#[test]
fn test_android_timezone_property_name_is_valid_cstr() {
CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
let mut invalid_property_name = ANDROID_TIMEZONE_PROPERTY_NAME.to_owned();
invalid_property_name.push(b'\0');
CStr::from_bytes_with_nul(&invalid_property_name).unwrap_err();
}
#[test]
fn test_android_timezone_property_name_getter() {
let key = android_timezone_property_name().to_bytes_with_nul();
assert_eq!(key, ANDROID_TIMEZONE_PROPERTY_NAME);
std::str::from_utf8(key).unwrap();
}
/// An exhaustive set of IANA timezone names for testing.
///
/// Pulled from Wikipedia as of Sat March 22, 2025:
///
/// - <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
/// - <https://en.wikipedia.org/w/index.php?title=List_of_tz_database_time_zones&oldid=1281103182>
static KNOWN_TIMEZONE_NAMES: &[&str] = &[
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmara",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Timbuktu",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Anguilla",
"America/Antigua",
"America/Argentina/ComodRivadavia",
"America/Aruba",
"America/Asuncion",
"America/Atka",
"America/Barbados",
"America/Belize",
"America/Bogota",
"America/Buenos_Aires",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Curacao",
"America/Dominica",
"America/El_Salvador",
"America/Ensenada",
"America/Fort_Wayne",
"America/Godthab",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guyana",
"America/Havana",
"America/Indianapolis",
"America/Jamaica",
"America/Jujuy",
"America/Knox_IN",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Louisville",
"America/Lower_Princes",
"America/Managua",
"America/Marigot",
"America/Martinique",
"America/Mendoza",
"America/Miquelon",
"America/Montevideo",
"America/Montreal",
"America/Montserrat",
"America/Nassau",
"America/Nipigon",
"America/Pangnirtung",
"America/Paramaribo",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Acre",
"America/Rainy_River",
"America/Rosario",
"America/Santa_Isabel",
"America/Santo_Domingo",
"America/Shiprock",
"America/St_Barthelemy",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Tegucigalpa",
"America/Thunder_Bay",
"America/Tortola",
"America/Virgin",
"America/Yellowknife",
"Antarctica/South_Pole",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Amman",
"Asia/Ashgabat",
"Asia/Ashkhabad",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Choibalsan",
"Asia/Chongqing",
"Asia/Chungking",
"Asia/Colombo",
"Asia/Dacca",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dushanbe",
"Asia/Harbin",
"Asia/Hong_Kong",
"Asia/Istanbul",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Karachi",
"Asia/Kashgar",
"Asia/Kathmandu",
"Asia/Katmandu",
"Asia/Kolkata",
"Asia/Kuwait",
"Asia/Macao",
"Asia/Macau",
"Asia/Manila",
"Asia/Muscat",
"Asia/Phnom_Penh",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Rangoon",
"Asia/Saigon",
"Asia/Seoul",
"Asia/Taipei",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Tel_Aviv",
"Asia/Thimbu",
"Asia/Thimphu",
"Asia/Ujung_Pandang",
"Asia/Ulan_Bator",
"Asia/Vientiane",
"Asia/Yangon",
"Asia/Yerevan",
"Atlantic/Bermuda",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Faroe",
"Atlantic/Jan_Mayen",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/ACT",
"Australia/Canberra",
"Australia/Currie",
"Australia/LHI",
"Australia/North",
"Australia/NSW",
"Australia/Queensland",
"Australia/South",
"Australia/Tasmania",
"Australia/Victoria",
"Australia/West",
"Australia/Yancowinna",
"Brazil/Acre",
"Brazil/DeNoronha",
"Brazil/East",
"Brazil/West",
"Canada/Atlantic",
"Canada/Central",
"Canada/Eastern",
"Canada/Mountain",
"Canada/Newfoundland",
"Canada/Pacific",
"Canada/Saskatchewan",
"Canada/Yukon",
"CET",
"Chile/Continental",
"Chile/EasterIsland",
"CST6CDT",
"Cuba",
"EET",
"Egypt",
"Eire",
"EST",
"EST5EDT",
"Etc/GMT",
"Etc/GMT+0",
"Etc/GMT+1",
"Etc/GMT+10",
"Etc/GMT+11",
"Etc/GMT+12",
"Etc/GMT+2",
"Etc/GMT+3",
"Etc/GMT+4",
"Etc/GMT+5",
"Etc/GMT+6",
"Etc/GMT+7",
"Etc/GMT+8",
"Etc/GMT+9",
"Etc/GMT-0",
"Etc/GMT-1",
"Etc/GMT-10",
"Etc/GMT-11",
"Etc/GMT-12",
"Etc/GMT-13",
"Etc/GMT-14",
"Etc/GMT-2",
"Etc/GMT-3",
"Etc/GMT-4",
"Etc/GMT-5",
"Etc/GMT-6",
"Etc/GMT-7",
"Etc/GMT-8",
"Etc/GMT-9",
"Etc/GMT0",
"Etc/Greenwich",
"Etc/UCT",
"Etc/Universal",
"Etc/UTC",
"Etc/Zulu",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Athens",
"Europe/Belfast",
"Europe/Belgrade",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kiev",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Nicosia",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Tiraspol",
"Europe/Uzhgorod",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zaporozhye",
"Factory",
"GB",
"GB-Eire",
"GMT",
"GMT+0",
"GMT-0",
"GMT0",
"Greenwich",
"Hongkong",
"HST",
"Iceland",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Iran",
"Israel",
"Jamaica",
"Japan",
"Kwajalein",
"Libya",
"MET",
"Mexico/BajaNorte",
"Mexico/BajaSur",
"Mexico/General",
"MST",
"MST7MDT",
"Navajo",
"NZ",
"NZ-CHAT",
"Pacific/Apia",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Guam",
"Pacific/Johnston",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Ponape",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Samoa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wallis",
"Pacific/Yap",
"Poland",
"Portugal",
"PRC",
"PST8PDT",
"ROC",
"ROK",
"Singapore",
"Turkey",
"UCT",
"Universal",
"US/Alaska",
"US/Aleutian",
"US/Arizona",
"US/Central",
"US/East-Indiana",
"US/Eastern",
"US/Hawaii",
"US/Indiana-Starke",
"US/Michigan",
"US/Mountain",
"US/Pacific",
"US/Samoa",
"UTC",
"W-SU",
"WET",
"Zulu",
"America/Rio_Branco",
"America/Maceio",
"America/Metlakatla",
"America/Juneau",
"America/Sitka",
"America/Adak",
"America/Yakutat",
"America/Anchorage",
"America/Nome",
"America/Manaus",
"America/Eirunepe",
"Asia/Aqtobe",
"America/Blanc-Sablon",
"America/Puerto_Rico",
"America/Goose_Bay",
"America/Moncton",
"America/Glace_Bay",
"America/Halifax",
"America/Noronha",
"Asia/Atyrau",
"Atlantic/Azores",
"America/Bahia",
"America/Bahia_Banderas",
"America/Tijuana",
"America/Mazatlan",
"Asia/Hovd",
"Asia/Shanghai",
"Asia/Makassar",
"Asia/Pontianak",
"Pacific/Bougainville",
"America/Fortaleza",
"America/Sao_Paulo",
"America/Argentina/Buenos_Aires",
"Europe/Busingen",
"Europe/Zurich",
"America/Merida",
"Atlantic/Canary",
"Antarctica/Casey",
"America/Argentina/Catamarca",
"America/Indiana/Tell_City",
"America/Indiana/Knox",
"America/Menominee",
"America/North_Dakota/Beulah",
"America/North_Dakota/New_Salem",
"America/North_Dakota/Center",
"America/Rankin_Inlet",
"America/Resolute",
"America/Winnipeg",
"America/Chicago",
"Africa/Maputo",
"America/Mexico_City",
"Africa/Ceuta",
"Pacific/Chatham",
"America/Chihuahua",
"America/Ojinaga",
"America/Ciudad_Juarez",
"Pacific/Chuuk",
"America/Matamoros",
"Europe/Simferopol",
"Asia/Dubai",
"America/Swift_Current",
"America/Regina",
"Antarctica/Davis",
"Africa/Lubumbashi",
"Africa/Kinshasa",
"Antarctica/DumontDUrville",
"America/Monterrey",
"Pacific/Easter",
"America/Indiana/Marengo",
"America/Indiana/Vincennes",
"America/Indiana/Indianapolis",
"America/Indiana/Petersburg",
"America/Indiana/Winamac",
"America/Indiana/Vevay",
"America/Kentucky/Louisville",
"America/Kentucky/Monticello",
"America/Detroit",
"America/Iqaluit",
"America/Toronto",
"America/New_York",
"America/Guayaquil",
"America/Atikokan",
"America/Panama",
"Asia/Tokyo",
"Pacific/Galapagos",
"Pacific/Gambier",
"Asia/Gaza",
"Pacific/Tarawa",
"Pacific/Honolulu",
"Asia/Jakarta",
"America/Argentina/Jujuy",
"Indian/Maldives",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"America/Argentina/La_Rioja",
"Pacific/Kiritimati",
"Australia/Lord_Howe",
"Antarctica/Macquarie",
"Atlantic/Madeira",
"Asia/Kuala_Lumpur",
"Asia/Aqtau",
"Pacific/Marquesas",
"America/Cuiaba",
"America/Campo_Grande",
"Antarctica/Mawson",
"America/Argentina/Mendoza",
"Pacific/Pago_Pago",
"Pacific/Midway",
"America/Argentina/Cordoba",
"America/Santiago",
"Asia/Nicosia",
"Europe/Berlin",
"America/Nuuk",
"Asia/Almaty",
"Pacific/Majuro",
"Asia/Ulaanbaatar",
"Europe/Kyiv",
"America/Edmonton",
"America/Boise",
"America/Inuvik",
"America/Cambridge_Bay",
"America/Denver",
"Europe/Kaliningrad",
"Europe/Kirov",
"Europe/Moscow",
"Europe/Volgograd",
"Europe/Astrakhan",
"Europe/Samara",
"Europe/Saratov",
"Europe/Ulyanovsk",
"Asia/Yekaterinburg",
"Asia/Omsk",
"Asia/Barnaul",
"Asia/Novokuznetsk",
"Asia/Krasnoyarsk",
"Asia/Novosibirsk",
"Asia/Tomsk",
"Asia/Irkutsk",
"Asia/Yakutsk",
"Asia/Khandyga",
"Asia/Chita",
"Asia/Vladivostok",
"Asia/Ust-Nera",
"Asia/Magadan",
"Asia/Srednekolymsk",
"Asia/Sakhalin",
"Asia/Anadyr",
"Asia/Kamchatka",
"America/Phoenix",
"America/Creston",
"America/Dawson_Creek",
"America/Fort_Nelson",
"America/Whitehorse",
"America/Dawson",
"America/Danmarkshavn",
"Asia/Jayapura",
"Australia/Sydney",
"Australia/Broken_Hill",
"Pacific/Auckland",
"Antarctica/McMurdo",
"America/St_Johns",
"Asia/Bangkok",
"Asia/Famagusta",
"Australia/Darwin",
"America/Los_Angeles",
"America/Vancouver",
"Antarctica/Palmer",
"Pacific/Port_Moresby",
"America/Belem",
"America/Santarem",
"Asia/Singapore",
"America/Recife",
"Pacific/Kanton",
"Pacific/Guadalcanal",
"Pacific/Pohnpei",
"Europe/Lisbon",
"Asia/Qostanay",
"Australia/Brisbane",
"Australia/Lindeman",
"America/Cancun",
"Asia/Qyzylorda",
"America/Punta_Arenas",
"America/Porto_Velho",
"America/Boa_Vista",
"Antarctica/Rothera",
"Asia/Kuching",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Rio_Gallegos",
"America/Scoresbysund",
"Pacific/Tahiti",
"America/Hermosillo",
"Australia/Adelaide",
"Asia/Ho_Chi_Minh",
"Europe/Madrid",
"Antarctica/Syowa",
"Asia/Riyadh",
"Australia/Hobart",
"America/Thule",
"America/Argentina/Ushuaia",
"America/Araguaina",
"Antarctica/Troll",
"America/Argentina/Tucuman",
"Asia/Tashkent",
"Asia/Samarkand",
"Australia/Melbourne",
"Antarctica/Vostok",
"Pacific/Wake",
"Africa/Lagos",
"Asia/Hebron",
"Asia/Oral",
"Australia/Eucla",
"Australia/Perth",
"Asia/Urumqi",
];
#[test]
fn test_tzname_buffer_fits_all_iana_names() {
let buf = tzname_buf();
let max_len = buf.len();
let mut failed_tz_names = vec![];
for &tz in KNOWN_TIMEZONE_NAMES {
// Require max_len + 1 to account for an optional NUL terminator.
if tz.len() >= max_len {
failed_tz_names.push(tz);
}
}
assert!(
failed_tz_names.is_empty(),
"One or more timezone names exceed the buffer length of {}. Max length of found timezone: {}\n{:?}",
max_len,
failed_tz_names.iter().map(|s| s.len()).max().unwrap(),
failed_tz_names
);
}
#[test]
fn test_tzname_buffer_correct_size() {
assert_eq!(
MAX_LEN, 64,
"Buffer length changed unexpectedly, ensure consistency with documented limit."
);
assert_eq!(
tzname_buf().len(),
MAX_LEN,
"Buffer length changed unexpectedly, ensure consistency with documented limit."
);
assert_eq!(
size_of_val(&tzname_buf()),
MAX_LEN,
"Buffer length changed unexpectedly, ensure consistency with documented limit."
);
}
}

View File

@@ -0,0 +1,119 @@
#![warn(clippy::all)]
#![warn(clippy::cargo)]
#![warn(clippy::undocumented_unsafe_blocks)]
#![allow(unknown_lints)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
//! get the IANA time zone for the current system
//!
//! This small utility crate provides the
//! [`get_timezone()`](fn.get_timezone.html) function.
//!
//! ```rust
//! // Get the current time zone as a string.
//! let tz_str = iana_time_zone::get_timezone()?;
//! println!("The current time zone is: {}", tz_str);
//! # Ok::<(), iana_time_zone::GetTimezoneError>(())
//! ```
//!
//! The resulting string can be parsed to a
//! [`chrono-tz::Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
//! variant like this:
//! ```rust
//! let tz_str = iana_time_zone::get_timezone()?;
//! let tz: chrono_tz::Tz = tz_str.parse()?;
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
#[allow(dead_code)]
mod ffi_utils;
#[cfg_attr(
any(all(target_os = "linux", not(target_env = "ohos")), target_os = "hurd"),
path = "tz_linux.rs"
)]
#[cfg_attr(all(target_os = "linux", target_env = "ohos"), path = "tz_ohos.rs")]
#[cfg_attr(target_os = "windows", path = "tz_windows.rs")]
#[cfg_attr(target_vendor = "apple", path = "tz_darwin.rs")]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
path = "tz_wasm32_unknown.rs"
)]
#[cfg_attr(
any(target_os = "freebsd", target_os = "dragonfly"),
path = "tz_freebsd.rs"
)]
#[cfg_attr(
any(target_os = "netbsd", target_os = "openbsd"),
path = "tz_netbsd.rs"
)]
#[cfg_attr(
any(target_os = "illumos", target_os = "solaris"),
path = "tz_illumos.rs"
)]
#[cfg_attr(target_os = "aix", path = "tz_aix.rs")]
#[cfg_attr(target_os = "android", path = "tz_android.rs")]
#[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")]
mod platform;
/// Error types
#[derive(Debug)]
pub enum GetTimezoneError {
/// Failed to parse
FailedParsingString,
/// Wrapped IO error
IoError(std::io::Error),
/// Platform-specific error from the operating system
OsError,
}
impl std::error::Error for GetTimezoneError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
GetTimezoneError::FailedParsingString => None,
GetTimezoneError::IoError(err) => Some(err),
GetTimezoneError::OsError => None,
}
}
}
impl std::fmt::Display for GetTimezoneError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(match self {
GetTimezoneError::FailedParsingString => "GetTimezoneError::FailedParsingString",
GetTimezoneError::IoError(err) => return err.fmt(f),
GetTimezoneError::OsError => "OsError",
})
}
}
impl From<std::io::Error> for GetTimezoneError {
fn from(orig: std::io::Error) -> Self {
GetTimezoneError::IoError(orig)
}
}
/// Get the current IANA time zone as a string.
///
/// See the module-level documentation for a usage example and more details
/// about this function.
#[inline]
pub fn get_timezone() -> Result<String, GetTimezoneError> {
platform::get_timezone_inner()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_current() {
println!("current: {}", get_timezone().unwrap());
}
}

View File

@@ -0,0 +1,9 @@
pub fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
Err(crate::GetTimezoneError::OsError)
}
#[cfg(not(feature = "fallback"))]
compile_error!(
"iana-time-zone is currently implemented for Linux, Window, MacOS, FreeBSD, NetBSD, \
OpenBSD, Dragonfly, WebAssembly (browser), iOS, Illumos, Android, AIX, Solaris and Haiku.",
);

View File

@@ -0,0 +1,7 @@
use std::env;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader};
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
env::var("TZ").map_err(|_| crate::GetTimezoneError::OsError)
}

View File

@@ -0,0 +1,27 @@
use std::sync::Once;
use android_system_properties::AndroidSystemProperties;
use crate::ffi_utils::android_timezone_property_name;
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
let key = android_timezone_property_name();
get_properties()
.and_then(|properties| properties.get_from_cstr(key))
.ok_or(crate::GetTimezoneError::OsError)
}
fn get_properties() -> Option<&'static AndroidSystemProperties> {
static INITIALIZED: Once = Once::new();
static mut PROPERTIES: Option<AndroidSystemProperties> = None;
INITIALIZED.call_once(|| {
let properties = AndroidSystemProperties::new();
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
unsafe { PROPERTIES = Some(properties) };
});
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
unsafe { PROPERTIES.as_ref() }
}

Some files were not shown because too many files have changed in this diff Show More