Bug 1900504 - Update the time crate to version 0.3.36 r=glandium,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D212497
This commit is contained in:
Alex Franchuk 2024-06-04 19:35:35 +00:00
parent cc7c900717
commit 0157246cee
109 changed files with 9097 additions and 3353 deletions

50
Cargo.lock generated
View file

@ -857,7 +857,7 @@ version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
dependencies = [
"time 0.3.23",
"time 0.3.36",
"version_check",
]
@ -1021,7 +1021,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"time 0.3.23",
"time 0.3.36",
"tokio",
"unic-langid",
"uuid",
@ -1337,6 +1337,16 @@ dependencies = [
"uuid",
]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
"serde",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.1"
@ -3820,7 +3830,7 @@ dependencies = [
"smallvec",
"stable_deref_trait",
"syn",
"time 0.3.23",
"time 0.3.36",
"time-macros",
"tinystr",
"tokio",
@ -3981,7 +3991,7 @@ dependencies = [
"env_logger",
"log",
"qlog",
"time 0.3.23",
"time 0.3.36",
"winapi",
]
@ -4165,6 +4175,12 @@ dependencies = [
"nsstring",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-derive"
version = "0.4.2"
@ -4507,10 +4523,16 @@ dependencies = [
"indexmap 2.2.6",
"line-wrap",
"serde",
"time 0.3.23",
"time 0.3.36",
"xml-rs",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -5761,11 +5783,14 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.23"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
@ -5773,16 +5798,17 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.10"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [
"num-conv",
"time-core",
]
@ -6494,7 +6520,7 @@ dependencies = [
"serde_derive",
"serde_json",
"thiserror",
"time 0.3.23",
"time 0.3.36",
"tokio",
"tokio-stream",
"url",

View file

@ -1498,6 +1498,16 @@ criteria = "safe-to-deploy"
version = "0.8.0"
notes = "This crates was written by Sentry and I've fully audited it as Firefox crash reporting machinery relies on it."
[[audits.deranged]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.3.11"
notes = """
This crate contains a decent bit of `unsafe` code, however all internal
unsafety is verified with copious assertions (many are compile-time), and
otherwise the unsafety is documented and left to the caller to verify.
"""
[[audits.derive_arbitrary]]
who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-run"
@ -2975,6 +2985,15 @@ criteria = "safe-to-deploy"
version = "0.4.2"
notes = "All code written or reviewed by Josh Stone."
[[audits.num-conv]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.1.0"
notes = """
Very straightforward, simple crate. No dependencies, unsafe, extern,
side-effectful std functions, etc.
"""
[[audits.num-derive]]
who = "Josh Stone <jistone@redhat.com>"
criteria = "safe-to-deploy"
@ -3249,6 +3268,15 @@ criteria = "safe-to-deploy"
version = "0.18.0"
notes = "Mozilla-developed package, no unsafe code, no access to file system, network or other far reaching APIs."
[[audits.powerfmt]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.2.0"
notes = """
A tiny bit of unsafe code to implement functionality that isn't in stable rust
yet, but it's all valid. Otherwise it's a pretty simple crate.
"""
[[audits.ppv-lite86]]
who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
@ -4085,6 +4113,16 @@ who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.3.17 -> 0.3.23"
[[audits.time]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.3.23 -> 0.3.36"
notes = """
There's a bit of new unsafe code that is self-imposed because they now assert
that ordinals are non-zero. All unsafe code was checked to ensure that the
invariants claimed were true.
"""
[[audits.time-core]]
who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
@ -4100,6 +4138,11 @@ who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.1.0 -> 0.1.1"
[[audits.time-core]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.1.1 -> 0.1.2"
[[audits.time-macros]]
who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
@ -4115,6 +4158,11 @@ who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.2.6 -> 0.2.10"
[[audits.time-macros]]
who = "Alex Franchuk <afranchuk@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.2.10 -> 0.2.18"
[[audits.tinystr]]
who = "Zibi Braniecki <zibi@unicode.org>"
criteria = "safe-to-deploy"

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"d1ee03b7033e382279ff580d89a70a9aaf163f977400f0899ad9624e24744e6f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","README.md":"fc4c9482d9e5225630da44e5371d6fa3f37220e2f4da2dac076cf4cd4f9592e7","src/lib.rs":"bc4b045c160d6f28726831d83f8389d9231410ae289a99950f63436219488dbb","src/tests.rs":"235e4f158084d12b0bfe85745c444d38bb134ebe584396d0a43154260f6576a7","src/traits.rs":"e3984e763afaa23dcf8ea686b473336472953b05abebc433acb26ab5f2237257","src/unsafe_wrapper.rs":"6e57697c2cd484cd60c1a50c4f4d32cb17526447c0f387d8ea3d89a2a89db688"},"package":"b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"}

83
third_party/rust/deranged/Cargo.toml vendored Normal file
View file

@ -0,0 +1,83 @@
# 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.67.0"
name = "deranged"
version = "0.3.11"
authors = ["Jacob Pratt <jacob@jhpratt.dev>"]
include = [
"src/**/*",
"LICENSE-*",
"README.md",
]
description = "Ranged integers"
readme = "README.md"
keywords = [
"integer",
"int",
"range",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/jhpratt/deranged"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docs_rs",
]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies.num-traits]
version = "0.2.15"
optional = true
default-features = false
[dependencies.powerfmt]
version = "0.2.0"
optional = true
default-features = false
[dependencies.quickcheck]
version = "1.0.3"
optional = true
default-features = false
[dependencies.rand]
version = "0.8.4"
optional = true
default-features = false
[dependencies.serde]
version = "1.0.126"
optional = true
default-features = false
[dev-dependencies.rand]
version = "0.8.4"
[dev-dependencies.serde_json]
version = "1.0.86"
[features]
alloc = []
default = ["std"]
num = ["dep:num-traits"]
powerfmt = ["dep:powerfmt"]
quickcheck = [
"dep:quickcheck",
"alloc",
]
rand = ["dep:rand"]
serde = ["dep:serde"]
std = ["alloc"]

202
third_party/rust/deranged/LICENSE-Apache vendored Normal file
View file

@ -0,0 +1,202 @@
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 2022 Jacob Pratt et al.
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.

19
third_party/rust/deranged/LICENSE-MIT vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2022 Jacob Pratt et al.
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.

3
third_party/rust/deranged/README.md vendored Normal file
View file

@ -0,0 +1,3 @@
# Deranged
This crate is a proof-of-concept implementation of ranged integers.

1474
third_party/rust/deranged/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load diff

688
third_party/rust/deranged/src/tests.rs vendored Normal file
View file

@ -0,0 +1,688 @@
use std::hash::Hash;
use crate::{
IntErrorKind, OptionRangedI128, OptionRangedI16, OptionRangedI32, OptionRangedI64,
OptionRangedI8, OptionRangedIsize, OptionRangedU128, OptionRangedU16, OptionRangedU32,
OptionRangedU64, OptionRangedU8, OptionRangedUsize, ParseIntError, RangedI128, RangedI16,
RangedI32, RangedI64, RangedI8, RangedIsize, RangedU128, RangedU16, RangedU32, RangedU64,
RangedU8, RangedUsize, TryFromIntError,
};
macro_rules! if_signed {
(signed $($x:tt)*) => { $($x)* };
(unsigned $($x:tt)*) => {};
}
macro_rules! if_unsigned {
(signed $($x:tt)*) => {};
(unsigned $($x:tt)*) => { $($x)* };
}
#[test]
fn errors() {
assert_eq!(
TryFromIntError.to_string(),
"out of range integral type conversion attempted"
);
assert_eq!(TryFromIntError.clone(), TryFromIntError);
assert_eq!(format!("{TryFromIntError:?}"), "TryFromIntError");
assert_eq!(
ParseIntError {
kind: IntErrorKind::Empty,
}
.to_string(),
"cannot parse integer from empty string"
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::InvalidDigit,
}
.to_string(),
"invalid digit found in string"
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::PosOverflow,
}
.to_string(),
"number too large to fit in target type"
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::NegOverflow,
}
.to_string(),
"number too small to fit in target type"
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::Zero,
}
.to_string(),
"number would be zero for non-zero type"
);
assert_eq!(
format!(
"{:?}",
ParseIntError {
kind: IntErrorKind::Empty
}
),
"ParseIntError { kind: Empty }"
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::Empty
}
.clone(),
ParseIntError {
kind: IntErrorKind::Empty
}
);
assert_eq!(
ParseIntError {
kind: IntErrorKind::Empty
}
.kind(),
&IntErrorKind::Empty
);
}
macro_rules! tests {
($($signed:ident $opt:ident $t:ident $inner:ident),* $(,)?) => {
#[test]
fn derives() {$(
assert_eq!($t::<5, 10>::MIN.clone(), $t::<5, 10>::MIN);
let mut hasher = std::collections::hash_map::DefaultHasher::new();
$t::<5, 10>::MIN.hash(&mut hasher);
assert_eq!(
$t::<5, 10>::MIN.cmp(&$t::<5, 10>::MAX),
std::cmp::Ordering::Less
);
assert_eq!($opt::<5, 10>::None.clone(), $opt::<5, 10>::None);
$opt::<5, 10>::None.hash(&mut hasher);
)*}
#[test]
fn expand() {$(
let expanded: $t::<0, 20> = $t::<5, 10>::MAX.expand();
assert_eq!(expanded, $t::<0, 20>::new_static::<10>());
)*}
#[test]
fn narrow() {$(
let narrowed: Option<$t::<10, 20>> = $t::<0, 20>::new_static::<10>().narrow();
assert_eq!(narrowed, Some($t::<10, 20>::MIN));
)*}
#[test]
fn new() {$(
assert!($t::<5, 10>::new(10).is_some());
assert!($t::<5, 10>::new(11).is_none());
)*}
#[test]
fn new_static() {$(
let six: $t::<5, 10> = $t::<5, 10>::new_static::<6>();
assert_eq!(Some(six), $t::<5, 10>::new(6));
)*}
#[test]
fn some_unchecked() {$(
// Safety: The value is in range.
unsafe {
assert_eq!($opt::<5, 10>::some_unchecked(10), $opt::Some($t::<5, 10>::MAX));
}
)*}
#[test]
fn is_some() {$(
assert!($opt::<5, 10>::Some($t::<5, 10>::MAX).is_some());
)*}
#[test]
fn is_none() {$(
assert!($opt::<5, 10>::None.is_none());
)*}
#[test]
fn default() {$(
assert_eq!($opt::<5, 10>::default(), $opt::<5, 10>::None);
)*}
#[test]
fn get() {$(
assert_eq!($t::<5, 10>::MAX.get(), 10);
assert_eq!($opt::<5, 10>::None.get(), None);
assert_eq!($opt::Some($t::<5, 10>::MAX).get(), Some($t::<5, 10>::MAX));
)*}
#[test]
fn get_primitive() {$(
assert_eq!($opt::Some($t::<5, 10>::MAX).get_primitive(), Some(10));
assert_eq!($opt::<5, 10>::None.get_primitive(), None);
)*}
#[test]
fn get_ref() {$(
assert_eq!($t::<5, 10>::MAX.get_ref(), &10);
)*}
#[test]
fn new_saturating() {$(
assert_eq!($t::<5, 10>::new_saturating(11), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::new_saturating(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::new_saturating(9), $t::<5, 10>::new_static::<9>());
)*}
#[test]
fn from_str_radix() {$(
assert_eq!($t::<5, 10>::from_str_radix("10", 10), Ok($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::from_str_radix("5", 10), Ok($t::<5, 10>::MIN));
assert_eq!(
$t::<5, 10>::from_str_radix("4", 10),
Err(ParseIntError { kind: IntErrorKind::NegOverflow }),
);
assert_eq!(
$t::<5, 10>::from_str_radix("11", 10),
Err(ParseIntError { kind: IntErrorKind::PosOverflow }),
);
assert_eq!(
$t::<5, 10>::from_str_radix("", 10),
Err(ParseIntError { kind: IntErrorKind::Empty }),
);
)*}
#[test]
fn checked_add() {$(
assert_eq!($t::<5, 10>::MAX.checked_add(1), None);
assert_eq!($t::<5, 10>::MAX.checked_add(0), Some($t::<5, 10>::MAX));
)*}
#[test]
fn unchecked_add() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MIN.unchecked_add(5), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_sub() {$(
assert_eq!($t::<5, 10>::MIN.checked_sub(1), None);
assert_eq!($t::<5, 10>::MIN.checked_sub(0), Some($t::<5, 10>::MIN));
)*}
#[test]
fn unchecked_sub() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_sub(5), $t::<5, 10>::MIN);
}
)*}
#[test]
fn checked_mul() {$(
assert_eq!($t::<5, 10>::MAX.checked_mul(2), None);
assert_eq!($t::<5, 10>::MAX.checked_mul(1), Some($t::<5, 10>::MAX));
)*}
#[test]
fn unchecked_mul() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_mul(1), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_div() {$(
assert_eq!($t::<5, 10>::MAX.checked_div(3), None);
assert_eq!($t::<5, 10>::MAX.checked_div(2), $t::<5, 10>::new(5));
assert_eq!($t::<5, 10>::MAX.checked_div(1), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MAX.checked_div(0), None);
)*}
#[test]
fn unchecked_div() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_div(1), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_div_euclid() {$(
assert_eq!($t::<5, 10>::MAX.checked_div_euclid(3), None);
assert_eq!($t::<5, 10>::MAX.checked_div_euclid(2), $t::<5, 10>::new(5));
assert_eq!($t::<5, 10>::MAX.checked_div_euclid(1), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MAX.checked_div_euclid(0), None);
)*}
#[test]
fn unchecked_div_euclid() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_div_euclid(1), $t::<5, 10>::MAX);
}
)*}
#[test]
fn rem() {$(if_unsigned! { $signed
assert_eq!($t::<5, 10>::MAX.rem($t::exact::<3>()), $t::<0, 3>::new_static::<1>());
assert_eq!($t::<5, 10>::MAX.rem($t::exact::<5>()), $t::<0, 5>::MIN);
})*}
#[test]
fn checked_rem() {$(
assert_eq!($t::<5, 10>::MAX.checked_rem(11), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MAX.checked_rem(5), None);
)*}
#[test]
fn unchecked_rem() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_rem(11), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_rem_euclid() {$(
assert_eq!($t::<5, 10>::MAX.checked_rem_euclid(11), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MAX.checked_rem_euclid(5), None);
)*}
#[test]
fn unchecked_rem_euclid() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_rem_euclid(11), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_neg() {$(
assert_eq!($t::<5, 10>::MIN.checked_neg(), None);
assert_eq!($t::<0, 10>::MIN.checked_neg(), Some($t::<0, 10>::MIN));
)*}
#[test]
fn unchecked_neg() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<0, 10>::MIN.unchecked_neg(), $t::<0, 10>::MIN);
}
)*}
#[test]
fn neg() {$( if_signed! { $signed
assert_eq!($t::<-10, 10>::MIN.neg(), $t::<-10, 10>::MAX);
})*}
#[test]
fn checked_shl() {$(
assert_eq!($t::<5, 10>::MAX.checked_shl(1), None);
assert_eq!($t::<5, 10>::MAX.checked_shl(0), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MIN.checked_shl(1), Some($t::<5, 10>::MAX));
)*}
#[test]
fn unchecked_shl() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_shl(0), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::MIN.unchecked_shl(1), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_shr() {$(
assert_eq!($t::<5, 10>::MAX.checked_shr(2), None);
assert_eq!($t::<5, 10>::MAX.checked_shr(1), Some($t::<5, 10>::MIN));
assert_eq!($t::<5, 10>::MAX.checked_shr(0), Some($t::<5, 10>::MAX));
)*}
#[test]
fn unchecked_shr() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_shr(1), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MAX.unchecked_shr(0), $t::<5, 10>::MAX);
}
)*}
#[test]
fn checked_abs() {$( if_signed! { $signed
assert_eq!($t::<5, 10>::MAX.checked_abs(), Some($t::<5, 10>::MAX));
assert_eq!($t::<-10, 10>::MIN.checked_abs(), Some($t::<-10, 10>::MAX));
assert_eq!($t::<-10, 0>::MIN.checked_abs(), None);
})*}
#[test]
fn unchecked_abs() { $(if_signed! { $signed
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_abs(), $t::<5, 10>::MAX);
assert_eq!($t::<-10, 10>::MIN.unchecked_abs(), $t::<-10, 10>::MAX);
}
})*}
#[test]
fn abs() { $(if_signed! { $signed
assert_eq!($t::<-5, 10>::MIN.abs().get(), 5);
})*}
#[test]
fn checked_pow() {$(
assert_eq!($t::<5, 10>::MAX.checked_pow(0), None);
assert_eq!($t::<5, 10>::MAX.checked_pow(1), Some($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::MAX.checked_pow(2), None);
)*}
#[test]
fn unchecked_pow() {$(
// Safety: The result is in range.
unsafe {
assert_eq!($t::<5, 10>::MAX.unchecked_pow(1), $t::<5, 10>::MAX);
}
)*}
#[test]
fn saturating_add() {$(
assert_eq!($t::<5, 10>::MAX.saturating_add(0), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::MAX.saturating_add(1), $t::<5, 10>::MAX);
)*}
#[test]
fn wrapping_add() {
$(
assert_eq!($t::<5, 10>::MAX.wrapping_add(0), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::MAX.wrapping_add(1), $t::<5, 10>::MIN);
assert_eq!($t::<{ $inner::MIN }, { $inner::MAX }>::MAX.wrapping_add(1),
$t::<{ $inner::MIN }, { $inner::MAX }>::MIN);
for i in 1..127 {
assert_eq!(
$t::<{ $inner::MIN}, { $inner::MAX - 1 }>::MAX.wrapping_add(i),
$t::<{ $inner::MIN}, { $inner::MAX - 1 }>::new($inner::MIN + i - 1).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, $inner::MAX + i ))
);
}
)*
$(if_signed! { $signed
for i in 1..=127 {
assert_eq!($t::<-5, 126>::MIN.wrapping_add(-i), $t::<-5,126>::new(126-i+1).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, 126-i+1)));
assert_eq!($t::<-5, 126>::MIN.wrapping_add(i), $t::<-5,126>::new(-5+i).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, 126-i+1)));
}
for i in -127..=-1 {
assert_eq!($t::<-5, 126>::MIN.wrapping_add(i), $t::<-5,126>::new(126+i+1).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, 126-i+1)));
assert_eq!($t::<-5, 126>::MIN.wrapping_add(-i), $t::<-5,126>::new(-5-i).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, 126-i+1)));
}
assert_eq!($t::<-5, 126>::MIN.wrapping_add(-128), $t::<-5,126>::new(-1).unwrap_or_else(|| panic!("adding 128+{} does not yield -1", $inner::MIN)));
assert_eq!($t::<-5, 10>::MAX.wrapping_add(0), $t::<-5, 10>::MAX);
assert_eq!($t::<-5, -3>::MIN.wrapping_add(-1-3), $t::<-5, -3>::MAX);
assert_eq!($t::<-5, -3>::MIN.wrapping_add(-1-30), $t::<-5, -3>::MAX);
assert_eq!($t::<-5, -3>::MIN.wrapping_add(30), $t::<-5, -3>::MIN);
assert_eq!($t::<-5, -3>::MIN.wrapping_add(-30), $t::<-5, -3>::MIN);
assert_eq!($t::<-5, 10>::MAX.wrapping_add(25), $t::<-5, 10>::MIN.wrapping_add(24));
assert_eq!($t::<-5, 10>::MIN.wrapping_add(24), $t::<-5, 10>::MIN.wrapping_add(8));
assert_eq!($t::<-5, 10>::MAX.wrapping_add(1), $t::<-5, 10>::MIN);
assert_eq!($t::<-5, 10>::MIN.wrapping_add(-1), $t::<-5, 10>::MAX);
assert_eq!($t::<-5, 127>::MIN.wrapping_add(-1), $t::<-5, 127>::MAX);
assert_eq!($t::<-127, 126>::MIN.wrapping_add(-1), $t::<-127, 126>::MAX);
assert_eq!($t::<{ $inner::MIN }, { $inner::MAX }>::MIN.wrapping_add(-1),
$t::<{ $inner::MIN }, { $inner::MAX }>::MAX);
})*
}
#[test]
fn wrapping_sub() {
$(
assert_eq!($t::<5, 10>::MIN.wrapping_sub(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.wrapping_sub(1), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::new(5 + 1).unwrap().wrapping_sub(1), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MAX.wrapping_sub(1), $t::<5, 10>::new(10 - 1).unwrap());
assert_eq!($t::<{ $inner::MIN }, { $inner::MAX }>::MIN.wrapping_sub(1),
$t::<{ $inner::MIN }, { $inner::MAX }>::MAX);
for i in 1..127 {
assert_eq!(
$t::<{ $inner::MIN + 1 }, { $inner::MAX }>::MIN.wrapping_sub(i),
$t::<{ $inner::MIN + 1 }, { $inner::MAX }>::new($inner::MAX - i + 1).unwrap_or_else(|| panic!("failed test at iteration {i}"))
);
}
)*
$(if_signed! { $signed
for i in -127..=127 {
assert_eq!($t::<-5, 126>::MIN.wrapping_add(i), $t::<-5,126>::MIN.wrapping_sub(-i), "failed test at {i}");
assert_eq!($t::<-5, 126>::MIN.wrapping_add(-i), $t::<-5,126>::MIN.wrapping_sub(i), "failed test at {i}");
}
assert_eq!(
$t::<-5, 126>::MIN.wrapping_add(127).wrapping_add(1),
$t::<-5,126>::MIN.wrapping_sub(-128)
);
assert_eq!(
$t::<-5, 126>::MIN.wrapping_add(-128),
$t::<-5,126>::MIN.wrapping_sub(127).wrapping_sub(1)
);
})*
}
#[test]
fn saturating_sub() {$(
assert_eq!($t::<5, 10>::MIN.saturating_sub(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.saturating_sub(1), $t::<5, 10>::MIN);
)*}
#[test]
fn saturating_neg() {$(if_signed! { $signed
assert_eq!($t::<5, 10>::MIN.saturating_neg(), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MAX.saturating_neg(), $t::<5, 10>::MIN);
assert_eq!($t::<-10, 0>::MIN.saturating_neg(), $t::<-10, 0>::MAX);
assert_eq!($t::<-10, 0>::MAX.saturating_neg(), $t::<-10, 0>::MAX);
})*}
#[test]
fn saturating_abs() {$(if_signed! { $signed
assert_eq!($t::<5, 10>::MIN.saturating_abs(), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MAX.saturating_abs(), $t::<5, 10>::MAX);
assert_eq!($t::<-10, 0>::MIN.saturating_abs(), $t::<-10, 0>::MAX);
assert_eq!($t::<-10, 0>::MAX.saturating_abs(), $t::<-10, 0>::MAX);
})*}
#[test]
fn saturating_mul() {$(
assert_eq!($t::<5, 10>::MIN.saturating_mul(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.saturating_mul(1), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.saturating_mul(2), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::MIN.saturating_mul(3), $t::<5, 10>::MAX);
)*}
#[test]
fn saturating_pow() {$(
assert_eq!($t::<5, 10>::MIN.saturating_pow(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.saturating_pow(1), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.saturating_pow(2), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::MIN.saturating_pow(3), $t::<5, 10>::MAX);
)*}
#[test]
fn as_ref() {$(
assert_eq!($t::<5, 10>::MIN.as_ref(), &5);
assert_eq!($t::<5, 10>::MAX.as_ref(), &10);
)*}
#[test]
fn borrow() {
use std::borrow::Borrow;
$(
assert_eq!(Borrow::<$inner>::borrow(&$t::<5, 10>::MIN), &5);
assert_eq!(Borrow::<$inner>::borrow(&$t::<5, 10>::MAX), &10);
)*
}
#[test]
fn formatting() {$(
let val = $t::<5, 10>::MAX;
assert_eq!(format!("{}", val), "10");
assert_eq!(format!("{:?}", val), "10");
assert_eq!(format!("{:b}", val), "1010");
assert_eq!(format!("{:o}", val), "12");
assert_eq!(format!("{:x}", val), "a");
assert_eq!(format!("{:X}", val), "A");
assert_eq!(format!("{:e}", val), "1e1");
assert_eq!(format!("{:E}", val), "1E1");
assert_eq!(format!("{:?}", $opt::Some($t::<5, 10>::MAX)), "Some(10)");
assert_eq!(format!("{:?}", $opt::<5, 10>::None), "None");
)*}
#[test]
fn ord() {$(
assert!($t::<5, 10>::MIN < $t::<5, 10>::MAX);
assert!($t::<5, 10>::MIN <= $t::<5, 10>::MAX);
assert!($t::<5, 10>::MAX > $t::<5, 10>::MIN);
assert!($t::<5, 10>::MAX >= $t::<5, 10>::MIN);
let none = $opt::<5, 10>::None;
let five = $opt::Some($t::<5, 10>::MIN);
let ten = $opt::Some($t::<5, 10>::MAX);
assert_eq!(none.cmp(&none), std::cmp::Ordering::Equal);
assert_eq!(five.cmp(&five), std::cmp::Ordering::Equal);
assert_eq!(ten.cmp(&ten), std::cmp::Ordering::Equal);
assert_eq!(none.cmp(&five), std::cmp::Ordering::Less);
assert_eq!(five.cmp(&ten), std::cmp::Ordering::Less);
assert_eq!(none.cmp(&ten), std::cmp::Ordering::Less);
assert_eq!(ten.cmp(&none), std::cmp::Ordering::Greater);
let none = $opt::<0, 10>::None;
let zero = $opt::Some($t::<0, 10>::MIN);
let ten = $opt::Some($t::<0, 10>::MAX);
assert_eq!(none.partial_cmp(&none), Some(std::cmp::Ordering::Equal));
assert_eq!(none.partial_cmp(&zero), Some(std::cmp::Ordering::Less));
assert_eq!(zero.partial_cmp(&ten), Some(std::cmp::Ordering::Less));
assert_eq!(none.partial_cmp(&ten), Some(std::cmp::Ordering::Less));
assert_eq!(ten.partial_cmp(&none), Some(std::cmp::Ordering::Greater));
)*}
#[test]
fn from() {$(
assert_eq!($inner::from($t::<5, 10>::MAX), 10);
assert_eq!($inner::from($t::<5, 10>::MIN), 5);
assert_eq!($opt::from($t::<5, 10>::MAX), $opt::Some($t::<5, 10>::MAX));
assert_eq!($opt::from(Some($t::<5, 10>::MAX)), $opt::Some($t::<5, 10>::MAX));
assert_eq!($opt::<5, 10>::from(None), $opt::<5, 10>::None);
assert_eq!(Option::from($opt::Some($t::<5, 10>::MAX)), Some($t::<5, 10>::MAX));
assert_eq!(Option::<$t<5, 10>>::from($opt::<5, 10>::None), None);
)*}
#[test]
fn try_from() {$(
assert_eq!($t::<5, 10>::try_from(10), Ok($t::<5, 10>::MAX));
assert_eq!($t::<5, 10>::try_from(5), Ok($t::<5, 10>::MIN));
assert_eq!($t::<5, 10>::try_from(4), Err(TryFromIntError));
assert_eq!($t::<5, 10>::try_from(11), Err(TryFromIntError));
)*}
#[test]
fn from_str() {$(
assert_eq!("10".parse::<$t<5, 10>>(), Ok($t::<5, 10>::MAX));
assert_eq!("5".parse::<$t<5, 10>>(), Ok($t::<5, 10>::MIN));
assert_eq!("4".parse::<$t<5, 10>>(), Err(ParseIntError { kind: IntErrorKind::NegOverflow }));
assert_eq!("11".parse::<$t<5, 10>>(), Err(ParseIntError { kind: IntErrorKind::PosOverflow }));
assert_eq!("".parse::<$t<5, 10>>(), Err(ParseIntError { kind: IntErrorKind::Empty }));
)*}
#[cfg(feature = "serde")]
#[test]
fn serde() -> serde_json::Result<()> {
$(
let val = $t::<5, 10>::MAX;
let serialized = serde_json::to_string(&val)?;
assert_eq!(serialized, "10");
let deserialized: $t<5, 10> = serde_json::from_str(&serialized)?;
assert_eq!(deserialized, val);
assert!(serde_json::from_str::<$t<5, 10>>("").is_err());
assert!(serde_json::from_str::<$t<5, 10>>("4").is_err());
assert!(serde_json::from_str::<$t<5, 10>>("11").is_err());
let val = $opt::<5, 10>::Some($t::<5, 10>::MAX);
let serialized = serde_json::to_string(&val)?;
assert_eq!(serialized, "10");
let deserialized: $opt<5, 10> = serde_json::from_str(&serialized)?;
assert_eq!(deserialized, val);
assert!(serde_json::from_str::<$opt<5, 10>>("").is_err());
assert!(serde_json::from_str::<$opt<5, 10>>("4").is_err());
assert!(serde_json::from_str::<$opt<5, 10>>("11").is_err());
let val = $opt::<5, 10>::None;
let serialized = serde_json::to_string(&val)?;
assert_eq!(serialized, "null");
assert!(serde_json::from_str::<$opt<5, 10>>("").is_err());
assert!(serde_json::from_str::<$opt<5, 10>>("4").is_err());
assert!(serde_json::from_str::<$opt<5, 10>>("11").is_err());
)*
Ok(())
}
#[cfg(feature = "rand")]
#[test]
fn rand() {$(
let rand_val: $t<5, 10> = rand::random();
assert!(rand_val >= $t::<5, 10>::MIN);
assert!(rand_val <= $t::<5, 10>::MAX);
let rand: $opt<5, 10> = rand::random();
if let Some(rand) = rand.get() {
assert!(rand >= $t::<5, 10>::MIN);
assert!(rand <= $t::<5, 10>::MAX);
}
)*}
#[cfg(feature = "num")]
#[test]
fn num() {$(
assert_eq!(<$t<5, 10> as num_traits::Bounded>::min_value(), $t::<5, 10>::MIN);
assert_eq!(<$t<5, 10> as num_traits::Bounded>::max_value(), $t::<5, 10>::MAX);
)*}
#[cfg(feature = "quickcheck")]
#[test]
fn quickcheck() {$(
#[allow(trivial_casts)]
quickcheck::quickcheck((|val| {
val >= $t::<5, 10>::MIN && val <= $t::<5, 10>::MAX
}) as fn($t<5, 10>) -> bool);
#[allow(trivial_casts)]
quickcheck::quickcheck((|val| {
if let Some(val) = val.get() {
val >= $t::<5, 10>::MIN && val <= $t::<5, 10>::MAX
} else {
true
}
}) as fn($opt<5, 10>) -> bool);
)*}
};
}
tests![
signed OptionRangedI8 RangedI8 i8,
signed OptionRangedI16 RangedI16 i16,
signed OptionRangedI32 RangedI32 i32,
signed OptionRangedI64 RangedI64 i64,
signed OptionRangedI128 RangedI128 i128,
signed OptionRangedIsize RangedIsize isize,
unsigned OptionRangedU8 RangedU8 u8,
unsigned OptionRangedU16 RangedU16 u16,
unsigned OptionRangedU32 RangedU32 u32,
unsigned OptionRangedU64 RangedU64 u64,
unsigned OptionRangedU128 RangedU128 u128,
unsigned OptionRangedUsize RangedUsize usize,
];

117
third_party/rust/deranged/src/traits.rs vendored Normal file
View file

@ -0,0 +1,117 @@
use crate::{
RangedI128, RangedI16, RangedI32, RangedI64, RangedI8, RangedIsize, RangedU128, RangedU16,
RangedU32, RangedU64, RangedU8, RangedUsize,
};
macro_rules! declare_traits {
($($trait_name:ident),* $(,)?) => {$(
pub(crate) trait $trait_name {
const ASSERT: ();
}
)*};
}
macro_rules! impl_traits_for_all {
($($ranged_ty:ident $inner_ty:ident),* $(,)?) => {$(
impl<const MIN: $inner_ty, const MAX: $inner_ty> RangeIsValid for $ranged_ty<MIN, MAX> {
const ASSERT: () = assert!(MIN <= MAX);
}
impl<
const CURRENT_MIN: $inner_ty,
const CURRENT_MAX: $inner_ty,
const NEW_MIN: $inner_ty,
const NEW_MAX: $inner_ty,
> ExpandIsValid for ($ranged_ty<CURRENT_MIN, CURRENT_MAX>, $ranged_ty<NEW_MIN, NEW_MAX>) {
const ASSERT: () = {
assert!(NEW_MIN <= CURRENT_MIN);
assert!(NEW_MAX >= CURRENT_MAX);
};
}
impl<
const CURRENT_MIN: $inner_ty,
const CURRENT_MAX: $inner_ty,
const NEW_MIN: $inner_ty,
const NEW_MAX: $inner_ty,
> NarrowIsValid for ($ranged_ty<CURRENT_MIN, CURRENT_MAX>, $ranged_ty<NEW_MIN, NEW_MAX>) {
const ASSERT: () = {
assert!(NEW_MIN >= CURRENT_MIN);
assert!(NEW_MAX <= CURRENT_MAX);
};
}
impl<
const VALUE: $inner_ty,
const MIN: $inner_ty,
const MAX: $inner_ty,
> StaticIsValid for ($ranged_ty<MIN, VALUE>, $ranged_ty<VALUE, MAX>) {
const ASSERT: () = {
assert!(VALUE >= MIN);
assert!(VALUE <= MAX);
};
}
)*};
}
macro_rules! impl_traits_for_signed {
($($ranged_ty:ident $inner_ty:ident),* $(,)?) => {$(
impl<const MIN: $inner_ty, const MAX: $inner_ty> AbsIsSafe for $ranged_ty<MIN, MAX> {
const ASSERT: () = {
assert!(MIN != <$inner_ty>::MIN);
assert!(-MIN <= MAX);
};
}
impl<const MIN: $inner_ty, const MAX: $inner_ty> NegIsSafe for $ranged_ty<MIN, MAX> {
const ASSERT: () = {
assert!(MIN != <$inner_ty>::MIN);
assert!(-MIN <= MAX);
assert!(-MAX >= MIN);
};
}
impl_traits_for_all!($ranged_ty $inner_ty);
)*};
}
macro_rules! impl_traits_for_unsigned {
($($ranged_ty:ident $inner_ty:ident),* $(,)?) => {$(
impl<const MIN: $inner_ty, const MAX: $inner_ty> AbsIsSafe for $ranged_ty<MIN, MAX> {
const ASSERT: () = ();
}
impl<const MIN: $inner_ty, const MAX: $inner_ty> NegIsSafe for $ranged_ty<MIN, MAX> {
const ASSERT: () = assert!(MAX == 0);
}
impl_traits_for_all!($ranged_ty $inner_ty);
)*};
}
declare_traits![
RangeIsValid,
AbsIsSafe,
NegIsSafe,
ExpandIsValid,
NarrowIsValid,
StaticIsValid,
];
impl_traits_for_signed! {
RangedI8 i8,
RangedI16 i16,
RangedI32 i32,
RangedI64 i64,
RangedI128 i128,
RangedIsize isize,
}
impl_traits_for_unsigned! {
RangedU8 u8,
RangedU16 u16,
RangedU32 u32,
RangedU64 u64,
RangedU128 u128,
RangedUsize usize,
}

View file

@ -0,0 +1,26 @@
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct Unsafe<T>(T);
impl<T: core::fmt::Debug> core::fmt::Debug for Unsafe<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl<T> Unsafe<T> {
pub(crate) const unsafe fn new(value: T) -> Self {
Self(value)
}
pub(crate) const fn get(&self) -> &T {
&self.0
}
}
impl<T> core::ops::Deref for Unsafe<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"c1d8999190f493d43b84b07eaffd2a9144e16be197bb3ef13eb69305e2f23047","LICENSE-Apache":"c0fd5f9df8d17e13587f8fe403d2326b835e60d532817d0b42ae4aea44209251","LICENSE-MIT":"af85fff507d80e6c7ff242acfc4b0a7f5de9a72286bb3c883c782772ca4b4402","src/lib.rs":"ab6c4b28902164204179f5c31473753fbe5220a4b23082e227478e19c2aa47ca"},"package":"51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"}

55
third_party/rust/num-conv/Cargo.toml vendored Normal file
View file

@ -0,0 +1,55 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.57.0"
name = "num-conv"
version = "0.1.0"
authors = ["Jacob Pratt <jacob@jhpratt.dev>"]
include = [
"src/**/*",
"LICENSE-*",
]
description = """
`num_conv` is a crate to convert between integer types without using `as` casts. This provides
better certainty when refactoring, makes the exact behavior of code more explicit, and allows using
turbofish syntax.
"""
readme = "README.md"
keywords = [
"cast",
"extend",
"truncate",
"convert",
"integer",
]
categories = [
"no-std",
"no-std::no-alloc",
"rust-patterns",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/jhpratt/num-conv"
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
[features]
[lints.clippy]
alloc-instead-of-core = "deny"
std-instead-of-core = "deny"
[lints.rust]
missing-docs = "warn"
unreachable-pub = "warn"
unused = "warn"

202
third_party/rust/num-conv/LICENSE-Apache vendored Normal file
View file

@ -0,0 +1,202 @@
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 2023 Jacob Pratt
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.

19
third_party/rust/num-conv/LICENSE-MIT vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2023 Jacob Pratt
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.

329
third_party/rust/num-conv/src/lib.rs vendored Normal file
View file

@ -0,0 +1,329 @@
//! `num_conv` is a crate to convert between integer types without using `as` casts. This provides
//! better certainty when refactoring, makes the exact behavior of code more explicit, and allows
//! using turbofish syntax.
#![no_std]
/// Anonymously import all extension traits.
///
/// This allows you to use the methods without worrying about polluting the namespace or importing
/// them individually.
///
/// ```rust
/// use num_conv::prelude::*;
/// ```
pub mod prelude {
pub use crate::{CastSigned as _, CastUnsigned as _, Extend as _, Truncate as _};
}
mod sealed {
pub trait Integer {}
macro_rules! impl_integer {
($($t:ty)*) => {$(
impl Integer for $t {}
)*};
}
impl_integer! {
u8 u16 u32 u64 u128 usize
i8 i16 i32 i64 i128 isize
}
pub trait ExtendTargetSealed<T> {
fn extend(self) -> T;
}
pub trait TruncateTargetSealed<T> {
fn truncate(self) -> T;
}
}
/// Cast to a signed integer of the same size.
///
/// This trait is implemented for all integers. Unsigned to signed casts are equivalent to
/// `0.wrapping_add_signed(value)`, while signed to signed casts are an identity conversion.
///
/// ```rust
/// # use num_conv::CastSigned;
/// assert_eq!(u8::MAX.cast_signed(), -1_i8);
/// assert_eq!(u16::MAX.cast_signed(), -1_i16);
/// assert_eq!(u32::MAX.cast_signed(), -1_i32);
/// assert_eq!(u64::MAX.cast_signed(), -1_i64);
/// assert_eq!(u128::MAX.cast_signed(), -1_i128);
/// assert_eq!(usize::MAX.cast_signed(), -1_isize);
/// ```
///
/// ```rust
/// # use num_conv::CastSigned;
/// assert_eq!(0_i8.cast_signed(), 0_i8);
/// assert_eq!(0_i16.cast_signed(), 0_i16);
/// assert_eq!(0_i32.cast_signed(), 0_i32);
/// assert_eq!(0_i64.cast_signed(), 0_i64);
/// assert_eq!(0_i128.cast_signed(), 0_i128);
/// assert_eq!(0_isize.cast_signed(), 0_isize);
/// ```
pub trait CastSigned: sealed::Integer {
/// The signed integer type with the same size as `Self`.
type Signed;
/// Cast an integer to the signed integer of the same size.
fn cast_signed(self) -> Self::Signed;
}
/// Cast to an unsigned integer of the same size.
///
/// This trait is implemented for all integers. Signed to unsigned casts are equivalent to
/// `0.wrapping_add_unsigned(value)`, while unsigned to unsigned casts are an identity conversion.
///
/// ```rust
/// # use num_conv::CastUnsigned;
/// assert_eq!((-1_i8).cast_unsigned(), u8::MAX);
/// assert_eq!((-1_i16).cast_unsigned(), u16::MAX);
/// assert_eq!((-1_i32).cast_unsigned(), u32::MAX);
/// assert_eq!((-1_i64).cast_unsigned(), u64::MAX);
/// assert_eq!((-1_i128).cast_unsigned(), u128::MAX);
/// assert_eq!((-1_isize).cast_unsigned(), usize::MAX);
/// ```
///
/// ```rust
/// # use num_conv::CastUnsigned;
/// assert_eq!(0_u8.cast_unsigned(), 0_u8);
/// assert_eq!(0_u16.cast_unsigned(), 0_u16);
/// assert_eq!(0_u32.cast_unsigned(), 0_u32);
/// assert_eq!(0_u64.cast_unsigned(), 0_u64);
/// assert_eq!(0_u128.cast_unsigned(), 0_u128);
/// assert_eq!(0_usize.cast_unsigned(), 0_usize);
/// ```
pub trait CastUnsigned: sealed::Integer {
/// The unsigned integer type with the same size as `Self`.
type Unsigned;
/// Cast an integer to the unsigned integer of the same size.
fn cast_unsigned(self) -> Self::Unsigned;
}
/// A type that can be used with turbofish syntax in [`Extend::extend`].
///
/// It is unlikely that you will want to use this trait directly. You are probably looking for the
/// [`Extend`] trait.
pub trait ExtendTarget<T>: sealed::ExtendTargetSealed<T> {}
/// A type that can be used with turbofish syntax in [`Truncate::truncate`].
///
/// It is unlikely that you will want to use this trait directly. You are probably looking for the
/// [`Truncate`] trait.
pub trait TruncateTarget<T>: sealed::TruncateTargetSealed<T> {}
/// Extend to an integer of the same size or larger, preserving its value.
///
/// ```rust
/// # use num_conv::Extend;
/// assert_eq!(0_u8.extend::<u16>(), 0_u16);
/// assert_eq!(0_u16.extend::<u32>(), 0_u32);
/// assert_eq!(0_u32.extend::<u64>(), 0_u64);
/// assert_eq!(0_u64.extend::<u128>(), 0_u128);
/// ```
///
/// ```rust
/// # use num_conv::Extend;
/// assert_eq!((-1_i8).extend::<i16>(), -1_i16);
/// assert_eq!((-1_i16).extend::<i32>(), -1_i32);
/// assert_eq!((-1_i32).extend::<i64>(), -1_i64);
/// assert_eq!((-1_i64).extend::<i128>(), -1_i128);
/// ```
pub trait Extend: sealed::Integer {
/// Extend an integer to an integer of the same size or larger, preserving its value.
fn extend<T>(self) -> T
where
Self: ExtendTarget<T>;
}
impl<T: sealed::Integer> Extend for T {
fn extend<U>(self) -> U
where
T: ExtendTarget<U>,
{
sealed::ExtendTargetSealed::extend(self)
}
}
/// Truncate to an integer of the same size or smaller, preserving the least significant bits.
///
/// ```rust
/// # use num_conv::Truncate;
/// assert_eq!(u16::MAX.truncate::<u8>(), u8::MAX);
/// assert_eq!(u32::MAX.truncate::<u16>(), u16::MAX);
/// assert_eq!(u64::MAX.truncate::<u32>(), u32::MAX);
/// assert_eq!(u128::MAX.truncate::<u64>(), u64::MAX);
/// ```
///
/// ```rust
/// # use num_conv::Truncate;
/// assert_eq!((-1_i16).truncate::<i8>(), -1_i8);
/// assert_eq!((-1_i32).truncate::<i16>(), -1_i16);
/// assert_eq!((-1_i64).truncate::<i32>(), -1_i32);
/// assert_eq!((-1_i128).truncate::<i64>(), -1_i64);
/// ```
pub trait Truncate: sealed::Integer {
/// Truncate an integer to an integer of the same size or smaller, preserving the least
/// significant bits.
fn truncate<T>(self) -> T
where
Self: TruncateTarget<T>;
}
impl<T: sealed::Integer> Truncate for T {
fn truncate<U>(self) -> U
where
T: TruncateTarget<U>,
{
sealed::TruncateTargetSealed::truncate(self)
}
}
macro_rules! impl_cast_signed {
($($($from:ty),+ => $to:ty;)*) => {$($(
const _: () = assert!(
core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
concat!(
"cannot cast ",
stringify!($from),
" to ",
stringify!($to),
" because they are different sizes"
)
);
impl CastSigned for $from {
type Signed = $to;
fn cast_signed(self) -> Self::Signed {
self as _
}
}
)+)*};
}
macro_rules! impl_cast_unsigned {
($($($from:ty),+ => $to:ty;)*) => {$($(
const _: () = assert!(
core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
concat!(
"cannot cast ",
stringify!($from),
" to ",
stringify!($to),
" because they are different sizes"
)
);
impl CastUnsigned for $from {
type Unsigned = $to;
fn cast_unsigned(self) -> Self::Unsigned {
self as _
}
}
)+)*};
}
macro_rules! impl_extend {
($($from:ty => $($to:ty),+;)*) => {$($(
const _: () = assert!(
core::mem::size_of::<$from>() <= core::mem::size_of::<$to>(),
concat!(
"cannot extend ",
stringify!($from),
" to ",
stringify!($to),
" because ",
stringify!($from),
" is larger than ",
stringify!($to)
)
);
impl sealed::ExtendTargetSealed<$to> for $from {
fn extend(self) -> $to {
self as _
}
}
impl ExtendTarget<$to> for $from {}
)+)*};
}
macro_rules! impl_truncate {
($($($from:ty),+ => $to:ty;)*) => {$($(
const _: () = assert!(
core::mem::size_of::<$from>() >= core::mem::size_of::<$to>(),
concat!(
"cannot truncate ",
stringify!($from),
" to ",
stringify!($to),
" because ",
stringify!($from),
" is smaller than ",
stringify!($to)
)
);
impl sealed::TruncateTargetSealed<$to> for $from {
fn truncate(self) -> $to {
self as _
}
}
impl TruncateTarget<$to> for $from {}
)+)*};
}
impl_cast_signed! {
u8, i8 => i8;
u16, i16 => i16;
u32, i32 => i32;
u64, i64 => i64;
u128, i128 => i128;
usize, isize => isize;
}
impl_cast_unsigned! {
u8, i8 => u8;
u16, i16 => u16;
u32, i32 => u32;
u64, i64 => u64;
u128, i128 => u128;
usize, isize => usize;
}
impl_extend! {
u8 => u8, u16, u32, u64, u128, usize;
u16 => u16, u32, u64, u128, usize;
u32 => u32, u64, u128;
u64 => u64, u128;
u128 => u128;
usize => usize;
i8 => i8, i16, i32, i64, i128, isize;
i16 => i16, i32, i64, i128, isize;
i32 => i32, i64, i128;
i64 => i64, i128;
i128 => i128;
isize => isize;
}
impl_truncate! {
u8, u16, u32, u64, u128, usize => u8;
u16, u32, u64, u128, usize => u16;
u32, u64, u128 => u32;
u64, u128 => u64;
u128 => u128;
usize => usize;
i8, i16, i32, i64, i128, isize => i8;
i16, i32, i64, i128, isize => i16;
i32, i64, i128 => i32;
i64, i128 => i64;
i128 => i128;
isize => isize;
}

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"59fa10abb1a34f70e61c97022938b02ec77ea0b161524f4a2599440bd3190c3b","LICENSE-Apache":"155420c6403d4e0fca34105e3c03fdd6939b64c393c7ec6f95f5b72c5474eab0","LICENSE-MIT":"070dbc7dda03a29296f2d58bdb9b7331af90f2abc9f31df22875d1eabaf29852","README.md":"188fa8a1323086828b9eeaf5a2031d66f066b617fc7dec318835a685d7c2e3c7","src/buf.rs":"b76bcb3daff67ed24e3e5fd958d98565c753107d368c3204ae0d70f9ca7394d4","src/ext.rs":"e6e5063f0006bfe92b59712032d4c6dfe1e1d302d8c92596f3eb7b42f747b9b4","src/lib.rs":"b7a6d061f8d79ed7d78088edefb5946d8eb911b1b523ef849f5f989533955b03","src/smart_display.rs":"44d89db6dbefc1b90e2e3e42279d9ae58f77baeab27a30cafebdb84bfdfaf03c","src/smart_display_impls.rs":"833c5dcb851f7979b222f4736e704ab50bba4d42ef264253dc57237381ef0e5b"},"package":"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"}

59
third_party/rust/powerfmt/Cargo.toml vendored Normal file
View file

@ -0,0 +1,59 @@
# 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.67.0"
name = "powerfmt"
version = "0.2.0"
authors = ["Jacob Pratt <jacob@jhpratt.dev>"]
description = """
`powerfmt` is a library that provides utilities for formatting values. This crate makes it
significantly easier to support filling to a minimum width with alignment, avoid heap
allocation, and avoid repetitive calculations.
"""
readme = "README.md"
keywords = [
"display",
"format",
"fmt",
"formatter",
"extension",
]
categories = [
"no-std",
"no-std::no-alloc",
"rust-patterns",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/jhpratt/powerfmt"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"__powerfmt_docs",
"--generate-link-to-definition",
]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies.powerfmt-macros]
version = "=0.1.0"
optional = true
[features]
alloc = []
default = [
"std",
"macros",
]
macros = ["dep:powerfmt-macros"]
std = ["alloc"]

202
third_party/rust/powerfmt/LICENSE-Apache vendored Normal file
View file

@ -0,0 +1,202 @@
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 2023 Jacob Pratt et al.
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.

19
third_party/rust/powerfmt/LICENSE-MIT vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2023 Jacob Pratt et al.
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.

45
third_party/rust/powerfmt/README.md vendored Normal file
View file

@ -0,0 +1,45 @@
# `powerfmt`
[![minimum rustc: 1.65](https://img.shields.io/badge/minimum%20rustc-1.65-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
[![version](https://img.shields.io/crates/v/powerfmt?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/powerfmt)
[![build status](https://img.shields.io/github/actions/workflow/status/jhpratt/powerfmt/build.yaml?branch=main&style=flat-square)](https://github.com/jhpratt/powerfmt/actions)
Documentation is available [on docs.rs](https://docs.rs/powerfmt).
## Minimum Rust version policy
`powerfmt` is guaranteed to compile with the latest stable release of Rust in addition to the two
prior minor releases. For example, if the latest stable Rust release is 1.70, then `powerfmt` is
guaranteed to compile with Rust 1.68, 1.69, and 1.70.
The minimum supported Rust version may be increased to one of the aforementioned versions if doing
so provides the end user a benefit. However, the minimum supported Rust version may also be bumped
to a version four minor releases prior to the most recent stable release if doing so improves code
quality or maintainability.
For interoperability with third-party crates, it is guaranteed that there exists a version of that
crate that supports the minimum supported Rust version of `powerfmt`. This does not mean that the
latest version of the third-party crate supports the minimum supported Rust version of `powerfmt`.
## Contributing
Contributions are always welcome! If you have an idea, it's best to float it by me before working on
it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out.
If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's
what I'm here for!
[Discussions]: https://github.com/jhpratt/powerfmt/discussions
## License
This project is licensed under either of
- [Apache License, Version 2.0](https://github.com/jhpratt/powerfmt/blob/main/LICENSE-Apache)
- [MIT license](https://github.com/jhpratt/powerfmt/blob/main/LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

198
third_party/rust/powerfmt/src/buf.rs vendored Normal file
View file

@ -0,0 +1,198 @@
//! A buffer for constructing a string while avoiding heap allocation.
use core::hash::{Hash, Hasher};
use core::mem::MaybeUninit;
use core::{fmt, str};
use crate::smart_display::{FormatterOptions, Metadata, SmartDisplay};
/// A buffer for construct a string while avoiding heap allocation.
///
/// The only requirement is that the buffer is large enough to hold the formatted string.
pub struct WriteBuffer<const SIZE: usize> {
buf: [MaybeUninit<u8>; SIZE],
len: usize,
}
impl<const SIZE: usize> fmt::Debug for WriteBuffer<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DisplayBuffer")
.field("buf", &self.as_str())
.field("remaining_capacity", &self.remaining_capacity())
.finish()
}
}
impl<const SIZE: usize> WriteBuffer<SIZE> {
/// Creates an empty buffer.
pub const fn new() -> Self {
Self {
buf: maybe_uninit_uninit_array::<_, SIZE>(),
len: 0,
}
}
/// Obtain the contents of the buffer as a string.
pub fn as_str(&self) -> &str {
self
}
/// Determine how many bytes are remaining in the buffer.
pub const fn remaining_capacity(&self) -> usize {
SIZE - self.len
}
}
impl<const SIZE: usize> Default for WriteBuffer<SIZE> {
fn default() -> Self {
Self::new()
}
}
impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialOrd<WriteBuffer<RIGHT_SIZE>>
for WriteBuffer<LEFT_SIZE>
{
fn partial_cmp(&self, other: &WriteBuffer<RIGHT_SIZE>) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialEq<WriteBuffer<RIGHT_SIZE>>
for WriteBuffer<LEFT_SIZE>
{
fn eq(&self, other: &WriteBuffer<RIGHT_SIZE>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const SIZE: usize> Eq for WriteBuffer<SIZE> {}
impl<const SIZE: usize> Ord for WriteBuffer<SIZE> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const SIZE: usize> Hash for WriteBuffer<SIZE> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<const SIZE: usize> AsRef<str> for WriteBuffer<SIZE> {
fn as_ref(&self) -> &str {
self
}
}
impl<const SIZE: usize> AsRef<[u8]> for WriteBuffer<SIZE> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const SIZE: usize> core::borrow::Borrow<str> for WriteBuffer<SIZE> {
fn borrow(&self) -> &str {
self
}
}
impl<const SIZE: usize> core::ops::Deref for WriteBuffer<SIZE> {
type Target = str;
fn deref(&self) -> &Self::Target {
// SAFETY: `buf` is only written to by the `fmt::Write::write_str` implementation which
// writes a valid UTF-8 string to `buf` and correctly sets `len`.
unsafe {
let s = maybe_uninit_slice_assume_init_ref(&self.buf[..self.len]);
str::from_utf8_unchecked(s)
}
}
}
impl<const SIZE: usize> fmt::Display for WriteBuffer<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
impl<const SIZE: usize> SmartDisplay for WriteBuffer<SIZE> {
type Metadata = ();
fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
Metadata::new(self.len, self, ())
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self)
}
}
impl<const SIZE: usize> fmt::Write for WriteBuffer<SIZE> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
if let Some(buf) = self.buf.get_mut(self.len..(self.len + bytes.len())) {
maybe_uninit_write_slice(buf, bytes);
self.len += bytes.len();
Ok(())
} else {
Err(fmt::Error)
}
}
}
/// Equivalent of [`MaybeUninit::uninit_array`] that compiles on stable.
#[must_use]
#[inline(always)]
const fn maybe_uninit_uninit_array<T, const N: usize>() -> [MaybeUninit<T>; N] {
// SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid.
unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() }
}
/// Equivalent of [`MaybeUninit::write_slice`] that compiles on stable.
fn maybe_uninit_write_slice<'a, T>(this: &'a mut [MaybeUninit<T>], src: &[T]) -> &'a mut [T]
where
T: Copy,
{
#[allow(trivial_casts)]
// SAFETY: T and MaybeUninit<T> have the same layout
let uninit_src = unsafe { &*(src as *const [T] as *const [MaybeUninit<T>]) };
this.copy_from_slice(uninit_src);
// SAFETY: Valid elements have just been copied into `this` so it is initialized
unsafe { maybe_uninit_slice_assume_init_mut(this) }
}
/// Equivalent of [`MaybeUninit::slice_assume_init_mut`] that compiles on stable.
///
/// # Safety
///
/// See [`MaybeUninit::slice_assume_init_mut`](https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.slice_assume_init_mut).
#[inline(always)]
unsafe fn maybe_uninit_slice_assume_init_mut<T, U>(slice: &mut [MaybeUninit<T>]) -> &mut [U] {
#[allow(trivial_casts)]
// SAFETY: similar to safety notes for `slice_get_ref`, but we have a mutable reference which is
// also guaranteed to be valid for writes.
unsafe {
&mut *(slice as *mut [MaybeUninit<T>] as *mut [U])
}
}
/// Equivalent of [`MaybeUninit::slice_assume_init_ref`] that compiles on stable.
///
/// # Safety
///
/// See [`MaybeUninit::slice_assume_init_ref`](https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.slice_assume_init_ref).
#[inline(always)]
const unsafe fn maybe_uninit_slice_assume_init_ref<T>(slice: &[MaybeUninit<T>]) -> &[T] {
#[allow(trivial_casts)]
// SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that `slice` is
// initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`. The pointer
// obtained is valid since it refers to memory owned by `slice` which is a reference and thus
// guaranteed to be valid for reads.
unsafe {
&*(slice as *const [MaybeUninit<T>] as *const [T])
}
}

54
third_party/rust/powerfmt/src/ext.rs vendored Normal file
View file

@ -0,0 +1,54 @@
//! Extension traits.
use core::fmt::{Alignment, Arguments, Formatter, Result, Write};
mod sealed {
pub trait Sealed {}
impl Sealed for core::fmt::Formatter<'_> {}
}
/// An extension trait for [`core::fmt::Formatter`].
pub trait FormatterExt: sealed::Sealed {
/// Writes the given arguments to the formatter, padding them with the given width. If `width`
/// is incorrect, the resulting output will not be the requested width.
fn pad_with_width(&mut self, width: usize, args: Arguments<'_>) -> Result;
}
impl FormatterExt for Formatter<'_> {
fn pad_with_width(&mut self, args_width: usize, args: Arguments<'_>) -> Result {
let Some(final_width) = self.width() else {
// The caller has not requested a width. Write the arguments as-is.
return self.write_fmt(args);
};
let Some(fill_width @ 1..) = final_width.checked_sub(args_width) else {
// No padding will be present. Write the arguments as-is.
return self.write_fmt(args);
};
let alignment = self.align().unwrap_or(Alignment::Left);
let fill = self.fill();
let left_fill_width = match alignment {
Alignment::Left => 0,
Alignment::Right => fill_width,
Alignment::Center => fill_width / 2,
};
let right_fill_width = match alignment {
Alignment::Left => fill_width,
Alignment::Right => 0,
// When the fill is not even on both sides, the extra fill goes on the right.
Alignment::Center => (fill_width + 1) / 2,
};
for _ in 0..left_fill_width {
self.write_char(fill)?;
}
self.write_fmt(args)?;
for _ in 0..right_fill_width {
self.write_char(fill)?;
}
Ok(())
}
}

15
third_party/rust/powerfmt/src/lib.rs vendored Normal file
View file

@ -0,0 +1,15 @@
//! `powerfmt` is a library that provides utilities for formatting values. Specifically, it makes it
//! significantly easier to support filling to a minimum width with alignment, avoid heap
//! allocation, and avoid repetitive calculations.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(__powerfmt_docs, feature(doc_auto_cfg, rustc_attrs))]
#![cfg_attr(__powerfmt_docs, allow(internal_features))]
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod buf;
pub mod ext;
pub mod smart_display;
mod smart_display_impls;

View file

@ -0,0 +1,695 @@
//! Definition of [`SmartDisplay`] and its related items.
//!
//! [`SmartDisplay`] is a trait that allows authors to provide additional information to both the
//! formatter and other users. This information is provided in the form of a metadata type. The only
//! required piece of metadata is the width of the value. This is _before_ it is passed to the
//! formatter (i.e. it does not include any padding added by the formatter). Other information
//! can be stored in a custom metadata type as needed. This information may be made available to
//! downstream users, but it is not required.
//!
//! This module contains the [`SmartDisplay`] and associated items.
//!
//! # Example
//!
//! ```rust
//! use std::fmt;
//!
//! use powerfmt::ext::FormatterExt as _;
//! use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};
//!
//! #[derive(Debug)]
//! struct User {
//! id: usize,
//! }
//!
//! // If you try to use `UserMetadata` in the `SmartDisplay` implementation, you will get a
//! // compiler error about a private type being used publicly. To avoid this, use this attribute to
//! // declare a private metadata type. You shouldn't need to worry about how this works, but be
//! // aware that any public fields or methods remain usable by downstream users.
//! #[smart_display::private_metadata]
//! struct UserMetadata {
//! username: String,
//! legal_name: String,
//! }
//!
//! // This attribute can be applied to `SmartDisplay` implementations. It will generate an
//! // implementation of `Display` that delegates to `SmartDisplay`, avoiding the need to write
//! // boilerplate.
//! #[smart_display::delegate]
//! impl SmartDisplay for User {
//! type Metadata = UserMetadata;
//!
//! fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
//! // This could be obtained from a database, for example.
//! let legal_name = "John Doe".to_owned();
//! let username = "jdoe".to_owned();
//!
//! // Note that this must be kept in sync with the implementation of `fmt_with_metadata`.
//! let width = smart_display::padded_width_of!(username, " (", legal_name, ")",);
//!
//! Metadata::new(
//! width,
//! self,
//! UserMetadata {
//! username,
//! legal_name,
//! },
//! )
//! }
//!
//! // Use the now-generated metadata to format the value. Here we use the `pad_with_width`
//! // method to use the alignment and desired width from the formatter.
//! fn fmt_with_metadata(
//! &self,
//! f: &mut fmt::Formatter<'_>,
//! metadata: Metadata<Self>,
//! ) -> fmt::Result {
//! f.pad_with_width(
//! metadata.unpadded_width(),
//! format_args!("{} ({})", metadata.username, metadata.legal_name),
//! )
//! }
//! }
//!
//! let user = User { id: 42 };
//! assert_eq!(user.to_string(), "jdoe (John Doe)");
//! assert_eq!(format!("{user:>20}"), " jdoe (John Doe)");
//! ```
use core::cmp;
use core::convert::Infallible;
use core::fmt::{Alignment, Debug, Display, Formatter, Result};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ops::Deref;
/// Compute the width of multiple items while optionally declaring the options for each item.
///
/// ```rust
/// # use powerfmt::smart_display;
/// let alpha = 0;
/// let beta = 1;
/// let gamma = 100;
///
/// let width = smart_display::padded_width_of!(
/// alpha, // use the default options
/// beta => width(2), // use the specified options
/// gamma => width(2) sign_plus(true), // use multiple options
/// );
/// assert_eq!(width, 7);
///
/// let formatted = format!("{alpha}{beta:2}{gamma:+2}");
/// assert_eq!(formatted.len(), width);
/// ```
///
/// Supported options are:
///
/// Option | Method called
/// --- | ---
/// `fill(char)` | [`FormatterOptions::with_fill`]
/// `sign_plus(bool)` | [`FormatterOptions::with_sign_plus`]
/// `sign_minus(bool)` | [`FormatterOptions::with_sign_minus`]
/// `align(Alignment)` | [`FormatterOptions::with_align`]
/// `width(usize)` | [`FormatterOptions::with_width`]
/// `precision(usize)` | [`FormatterOptions::with_precision`]
/// `alternate(bool)` | [`FormatterOptions::with_alternate`]
/// `sign_aware_zero_pad(bool)` | [`FormatterOptions::with_sign_aware_zero_pad`]
///
/// If there are future additions to [`FormatterOptions`], they will be added to this macro as well.
///
/// Options may be provided in any order and will be called in the order they are provided. The
/// ordering matters if providing both `sign_plus` and `sign_minus`.
#[cfg(doc)]
#[doc(hidden)] // Don't show at crate root.
#[macro_export]
macro_rules! padded_width_of {
($($t:tt)*) => {};
}
#[cfg(not(doc))]
#[allow(missing_docs)] // This is done with `#[cfg(doc)]` to avoid showing the various rules.
#[macro_export]
macro_rules! __not_public_at_root__padded_width_of {
// Base case
(@inner [] [$($output:tt)+]) => { $($output)+ };
(@inner [$e:expr $(, $($remaining:tt)*)?] [$($expansion:tt)+]) => {
$crate::smart_display::padded_width_of!(@inner [$($($remaining)*)?] [
$($expansion)+ + $crate::smart_display::Metadata::padded_width_of(
&$e,
$crate::smart_display::padded_width_of!(@options)
)
])
};
(@inner
[$e:expr => $($call:ident($call_expr:expr))+ $(, $($remaining:tt)*)?]
[$($expansion:tt)+]
) => {
$crate::smart_display::padded_width_of!(@inner [$($($remaining)*)?] [
$($expansion)+ + $crate::smart_display::Metadata::padded_width_of(
&$e,
*$crate::smart_display::padded_width_of!(@options $($call($call_expr))+)
)
])
};
// Options base case
(@options_inner [] [$($output:tt)+]) => { $($output)+ };
(@options_inner [fill($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_fill($e)
])
};
(@options_inner [sign_plus($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_sign_plus($e)
])
};
(@options_inner [sign_minus($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_sign_minus($e)
])
};
(@options_inner [align($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_align(Some($e))
])
};
(@options_inner [width($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_width(Some($e))
])
};
(@options_inner [precision($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_precision(Some($e))
])
};
(@options_inner [alternate($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_width($e)
])
};
(@options_inner [sign_aware_zero_pad($e:expr) $($remaining:tt)*] [$($expansion:tt)*]) => {
$crate::smart_display::padded_width_of!(@options_inner [$($remaining)*] [
$($expansion)*.with_sign_aware_zero_pad($e)
])
};
// Options entry point
(@options $($e:tt)*) => {
$crate::smart_display::padded_width_of!(@options_inner [$($e)*] [
$crate::smart_display::FormatterOptions::default()
])
};
// Entry point
($($t:tt)*) => {
$crate::smart_display::padded_width_of!(
@inner [$($t)*] [0]
)
};
}
#[cfg(not(doc))]
pub use __not_public_at_root__padded_width_of as padded_width_of;
#[cfg(doc)]
#[doc(inline)] // Show in this module.
pub use padded_width_of;
/// Implement [`Display`] for a type by using its implementation of [`SmartDisplay`].
///
/// This attribute is applied to the `SmartDisplay` implementation.
///
/// ```rust,no_run
/// # use powerfmt::smart_display::{self, SmartDisplay, Metadata, FormatterOptions};
/// # struct Foo;
/// #[smart_display::delegate]
/// impl SmartDisplay for Foo {
/// # type Metadata = ();
/// # fn metadata(&self, f: FormatterOptions) -> Metadata<Self> {
/// # todo!()
/// # }
/// // ...
/// }
/// ```
#[cfg(feature = "macros")]
pub use powerfmt_macros::smart_display_delegate as delegate;
/// Declare a private metadata type for `SmartDisplay`.
///
/// Use this attribute if you want to provide metadata for a type that is not public. Doing
/// this will avoid a compiler error about a private type being used publicly. Keep in mind
/// that any public fields, public methods, and trait implementations _will_ be able to be used
/// by downstream users.
///
/// To avoid accidentally exposing details, such as when all fields are public or if the type
/// is a unit struct, the type is annotated with `#[non_exhaustive]` automatically.
///
/// ```rust,no_run
/// # use powerfmt::smart_display;
/// /// Metadata for `Foo`
/// #[smart_display::private_metadata]
/// #[derive(Debug)]
/// pub(crate) struct FooMetadata {
/// pub(crate) expensive_to_calculate: usize,
/// }
/// ```
#[cfg(feature = "macros")]
pub use powerfmt_macros::smart_display_private_metadata as private_metadata;
#[derive(Debug)]
enum FlagBit {
SignPlus,
SignMinus,
Alternate,
SignAwareZeroPad,
WidthIsInitialized,
PrecisionIsInitialized,
}
/// Configuration for formatting.
///
/// This struct is obtained from a [`Formatter`]. It provides the same functionality as that of a
/// reference to a `Formatter`. However, it is not possible to construct a `Formatter`, which is
/// necessary for some use cases of [`SmartDisplay`]. `FormatterOptions` implements [`Default`] and
/// has builder methods to alleviate this.
#[derive(Clone, Copy)]
pub struct FormatterOptions {
flags: u8,
fill: char,
align: Option<Alignment>,
width: MaybeUninit<usize>,
precision: MaybeUninit<usize>,
}
impl Debug for FormatterOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("FormatterOptions")
.field("fill", &self.fill)
.field("align", &self.align())
.field("width", &self.width())
.field("precision", &self.precision())
.field("sign_plus", &self.sign_plus())
.field("sign_minus", &self.sign_minus())
.field("alternate", &self.alternate())
.field("sign_aware_zero_pad", &self.sign_aware_zero_pad())
.finish()
}
}
impl Default for FormatterOptions {
#[inline]
fn default() -> Self {
Self {
flags: 0,
fill: ' ',
align: None,
width: MaybeUninit::uninit(),
precision: MaybeUninit::uninit(),
}
}
}
impl FormatterOptions {
/// Sets the fill character to use whenever there is alignment.
#[inline]
pub fn with_fill(&mut self, c: char) -> &mut Self {
self.fill = c;
self
}
/// Set whether the `+` flag is specified.
#[inline]
pub fn with_sign_plus(&mut self, b: bool) -> &mut Self {
if b {
self.flags |= 1 << FlagBit::SignPlus as u8;
self.flags &= !(1 << FlagBit::SignMinus as u8);
} else {
self.flags &= !(1 << FlagBit::SignPlus as u8);
}
self
}
/// Set whether the `-` flag is specified.
#[inline]
pub fn with_sign_minus(&mut self, b: bool) -> &mut Self {
if b {
self.flags |= 1 << FlagBit::SignMinus as u8;
self.flags &= !(1 << FlagBit::SignPlus as u8);
} else {
self.flags &= !(1 << FlagBit::SignMinus as u8);
}
self
}
/// Set the flag indicating what form of alignment is requested, if any.
#[inline]
pub fn with_align(&mut self, align: Option<Alignment>) -> &mut Self {
self.align = align;
self
}
/// Set the optional integer width that the output should be.
#[inline]
pub fn with_width(&mut self, width: Option<usize>) -> &mut Self {
if let Some(width) = width {
self.flags |= 1 << FlagBit::WidthIsInitialized as u8;
self.width = MaybeUninit::new(width);
} else {
self.flags &= !(1 << FlagBit::WidthIsInitialized as u8);
}
self
}
/// Set the optional precision for numeric types. Alternatively, the maximum width for string
/// types.
#[inline]
pub fn with_precision(&mut self, precision: Option<usize>) -> &mut Self {
if let Some(precision) = precision {
self.flags |= 1 << FlagBit::PrecisionIsInitialized as u8;
self.precision = MaybeUninit::new(precision);
} else {
self.flags &= !(1 << FlagBit::PrecisionIsInitialized as u8);
}
self
}
/// Set whether the `#` flag is specified.
#[inline]
pub fn with_alternate(&mut self, b: bool) -> &mut Self {
if b {
self.flags |= 1 << FlagBit::Alternate as u8;
} else {
self.flags &= !(1 << FlagBit::Alternate as u8);
}
self
}
/// Set whether the `0` flag is specified.
#[inline]
pub fn with_sign_aware_zero_pad(&mut self, b: bool) -> &mut Self {
if b {
self.flags |= 1 << FlagBit::SignAwareZeroPad as u8;
} else {
self.flags &= !(1 << FlagBit::SignAwareZeroPad as u8);
}
self
}
}
impl FormatterOptions {
/// Character used as 'fill' whenever there is alignment.
#[inline]
#[must_use]
pub const fn fill(&self) -> char {
self.fill
}
/// Flag indicating what form of alignment was requested.
#[inline]
#[must_use]
pub const fn align(&self) -> Option<Alignment> {
self.align
}
/// Optionally specified integer width that the output should be.
#[inline]
#[must_use]
pub const fn width(&self) -> Option<usize> {
if (self.flags >> FlagBit::WidthIsInitialized as u8) & 1 == 1 {
// Safety: `width` is initialized if the flag is set.
Some(unsafe { self.width.assume_init() })
} else {
None
}
}
/// Optionally specified precision for numeric types. Alternatively, the maximum width for
/// string types.
#[inline]
#[must_use]
pub const fn precision(&self) -> Option<usize> {
if (self.flags >> FlagBit::PrecisionIsInitialized as u8) & 1 == 1 {
// Safety: `precision` is initialized if the flag is set.
Some(unsafe { self.precision.assume_init() })
} else {
None
}
}
/// Determines if the `+` flag was specified.
#[inline]
#[must_use]
pub const fn sign_plus(&self) -> bool {
(self.flags >> FlagBit::SignPlus as u8) & 1 == 1
}
/// Determines if the `-` flag was specified.
#[inline]
#[must_use]
pub const fn sign_minus(&self) -> bool {
(self.flags >> FlagBit::SignMinus as u8) & 1 == 1
}
/// Determines if the `#` flag was specified.
#[inline]
#[must_use]
pub const fn alternate(&self) -> bool {
(self.flags >> FlagBit::Alternate as u8) & 1 == 1
}
/// Determines if the `0` flag was specified.
#[inline]
#[must_use]
pub const fn sign_aware_zero_pad(&self) -> bool {
(self.flags >> FlagBit::SignAwareZeroPad as u8) & 1 == 1
}
}
impl From<&Formatter<'_>> for FormatterOptions {
fn from(value: &Formatter<'_>) -> Self {
*Self::default()
.with_fill(value.fill())
.with_sign_plus(value.sign_plus())
.with_sign_minus(value.sign_minus())
.with_align(value.align())
.with_width(value.width())
.with_precision(value.precision())
.with_alternate(value.alternate())
.with_sign_aware_zero_pad(value.sign_aware_zero_pad())
}
}
impl From<&mut Formatter<'_>> for FormatterOptions {
#[inline]
fn from(value: &mut Formatter<'_>) -> Self {
(&*value).into()
}
}
/// Information used to format a value. This is returned by [`SmartDisplay::metadata`].
///
/// This type is generic over any user-provided type. This allows the author to store any
/// information that is needed. For example, a type's implementation of [`SmartDisplay`] may need
/// to calculate something before knowing its width. This calculation can be performed, with the
/// result being stored in the custom metadata type.
///
/// Note that `Metadata` _always_ contains the width of the type. Authors do not need to store this
/// information in their custom metadata type.
///
/// Generally speaking, a type should be able to be formatted using only its metadata, fields, and
/// the formatter. Any other information should be stored in the metadata type.
pub struct Metadata<'a, T>
where
T: SmartDisplay + ?Sized,
{
unpadded_width: usize,
metadata: T::Metadata,
_value: PhantomData<&'a T>, // variance
}
// manual impls for bounds
impl<T> Debug for Metadata<'_, T>
where
T: SmartDisplay,
T::Metadata: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("Metadata")
.field("unpadded_width", &self.unpadded_width)
.field("metadata", &self.metadata)
.finish()
}
}
impl<T> Clone for Metadata<'_, T>
where
T: SmartDisplay,
T::Metadata: Clone,
{
fn clone(&self) -> Self {
Self {
unpadded_width: self.unpadded_width,
metadata: self.metadata.clone(),
_value: self._value,
}
}
}
impl<T> Copy for Metadata<'_, T>
where
T: SmartDisplay,
T::Metadata: Copy,
{
}
impl<'a, T> Metadata<'a, T>
where
T: SmartDisplay + ?Sized,
{
/// Creates a new `Metadata` with the given width and metadata. While the width _should_ be
/// exact, this is not a requirement for soundness.
pub const fn new(unpadded_width: usize, _value: &T, metadata: T::Metadata) -> Self {
Self {
unpadded_width,
metadata,
_value: PhantomData,
}
}
/// Reuse the metadata for another type. This is useful when implementing [`SmartDisplay`] for a
/// type that wraps another type. Both type's metadata type must be the same.
pub fn reuse<'b, U>(self) -> Metadata<'b, U>
where
'a: 'b,
U: SmartDisplay<Metadata = T::Metadata> + ?Sized,
{
Metadata {
unpadded_width: self.unpadded_width,
metadata: self.metadata,
_value: PhantomData,
}
}
/// Obtain the width of the value before padding.
pub const fn unpadded_width(&self) -> usize {
self.unpadded_width
}
/// Obtain the width of the value after padding.
pub fn padded_width(&self, f: FormatterOptions) -> usize {
match f.width() {
Some(requested_width) => cmp::max(self.unpadded_width(), requested_width),
None => self.unpadded_width(),
}
}
}
impl Metadata<'_, Infallible> {
/// Obtain the width of the value before padding, given the formatter options.
pub fn unpadded_width_of<T>(value: T, f: FormatterOptions) -> usize
where
T: SmartDisplay,
{
value.metadata(f).unpadded_width
}
/// Obtain the width of the value after padding, given the formatter options.
pub fn padded_width_of<T>(value: T, f: FormatterOptions) -> usize
where
T: SmartDisplay,
{
value.metadata(f).padded_width(f)
}
}
/// Permit using `Metadata` as a smart pointer to the user-provided metadata.
impl<T> Deref for Metadata<'_, T>
where
T: SmartDisplay + ?Sized,
{
type Target = T::Metadata;
fn deref(&self) -> &T::Metadata {
&self.metadata
}
}
/// Format trait that allows authors to provide additional information.
///
/// This trait is similar to [`Display`], but allows the author to provide additional information
/// to the formatter. This information is provided in the form of a custom metadata type.
///
/// The only required piece of metadata is the width of the value. This is _before_ it is passed to
/// the formatter (i.e. it does not include any padding added by the formatter). Other information
/// can be stored in a custom metadata type as needed. This information may be made available to
/// downstream users, but it is not required.
///
/// **Note**: While both `fmt_with_metadata` and `fmt` have default implementations, it is strongly
/// recommended to implement only `fmt_with_metadata`. `fmt` should be implemented if and only if
/// the type does not require any of the calculated metadata. In that situation, `fmt_with_metadata`
/// should be omitted.
#[cfg_attr(__powerfmt_docs, rustc_must_implement_one_of(fmt, fmt_with_metadata))]
pub trait SmartDisplay: Display {
/// User-provided metadata type.
type Metadata;
/// Compute any information needed to format the value. This must, at a minimum, determine the
/// width of the value before any padding is added by the formatter.
///
/// If the type uses other types that implement `SmartDisplay` verbatim, the inner types should
/// have their metadata calculated and included in the returned value.
///
/// # Lifetimes
///
/// This method's return type contains a lifetime to `self`. This ensures that the metadata will
/// neither outlive the value nor be invalidated by a mutation of the value (barring interior
/// mutability).
///
/// ```rust,compile_fail
/// # use std::fmt;
/// # use std::fmt::Write;
/// # use powerfmt::buf::WriteBuffer;
/// # use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};
/// #[derive(Debug)]
/// struct WrappedBuffer(WriteBuffer<128>);
///
/// #[smart_display::delegate]
/// impl SmartDisplay for WrappedBuffer {
/// type Metadata = ();
///
/// fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
/// Metadata::new(self.0.len(), self, ())
/// }
///
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// f.pad(self.0.as_str())
/// }
/// }
///
/// let mut buf = WrappedBuffer(WriteBuffer::new());
/// let metadata = buf.metadata(FormatterOptions::default());
/// // We cannot mutate the buffer while it is borrowed and use its previous metadata on the
/// // following line.
/// write!(buf.0, "Hello, world!")?;
/// assert_eq!(metadata.width(), 13);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self>;
/// Format the value using the given formatter and metadata. The formatted output should have
/// the width indicated by the metadata. This is before any padding is added by the
/// formatter.
///
/// If the metadata is not needed, you should implement the `fmt` method instead.
fn fmt_with_metadata(&self, f: &mut Formatter<'_>, _metadata: Metadata<'_, Self>) -> Result {
SmartDisplay::fmt(self, f)
}
/// Format the value using the given formatter. This is the same as [`Display::fmt`].
///
/// The default implementation of this method calls `fmt_with_metadata` with the result of
/// `metadata`. Generally speaking, this method should not be implemented. You should implement
/// the `fmt_with_metadata` method instead.
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let metadata = self.metadata(f.into());
self.fmt_with_metadata(f, metadata)
}
}

View file

@ -0,0 +1,303 @@
//! Implementation of [`SmartDisplay`] for various types.
#[cfg(feature = "alloc")]
use alloc::borrow::{Cow, ToOwned};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::rc::Rc;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use alloc::sync::Arc;
use core::cell::{Ref, RefMut};
use core::cmp::min;
use core::convert::Infallible;
use core::fmt::{self, Display, Formatter};
use core::num::Wrapping;
use core::pin::Pin;
use crate::smart_display::{FormatterOptions, Metadata, SmartDisplay};
impl SmartDisplay for Infallible {
type Metadata = Self;
#[inline]
fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
match *self {}
}
#[inline]
fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
match *self {}
}
}
impl SmartDisplay for bool {
type Metadata = ();
#[inline]
fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
Metadata::new(if *self { 4 } else { 5 }, self, ())
}
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl SmartDisplay for str {
type Metadata = ();
#[inline]
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
Metadata::new(
match f.precision() {
Some(max_len) => min(self.len(), max_len),
None => self.len(),
},
self,
(),
)
}
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(feature = "alloc")]
impl SmartDisplay for String {
type Metadata = ();
#[inline]
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(feature = "alloc")]
impl<'a, B, O> SmartDisplay for Cow<'a, B>
where
B: SmartDisplay + ToOwned<Owned = O> + ?Sized,
O: SmartDisplay<Metadata = B::Metadata> + 'a,
{
type Metadata = B::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
match *self {
Cow::Borrowed(ref b) => b.metadata(f).reuse(),
Cow::Owned(ref o) => o.metadata(f).reuse(),
}
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<T> SmartDisplay for Pin<&T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
self.get_ref().metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self.get_ref(), f)
}
}
impl<T> SmartDisplay for &T
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(*self, f)
}
}
impl<T> SmartDisplay for &mut T
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(*self, f)
}
}
impl<T> SmartDisplay for Ref<'_, T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&**self, f)
}
}
impl<T> SmartDisplay for RefMut<'_, T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&**self, f)
}
}
impl<T> SmartDisplay for Wrapping<T>
where
T: SmartDisplay,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
self.0.metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&self.0, f)
}
}
#[cfg(feature = "alloc")]
impl<T> SmartDisplay for Rc<T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&**self, f)
}
}
#[cfg(feature = "alloc")]
impl<T> SmartDisplay for Arc<T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&**self, f)
}
}
#[cfg(feature = "alloc")]
impl<T> SmartDisplay for Box<T>
where
T: SmartDisplay + ?Sized,
{
type Metadata = T::Metadata;
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
(**self).metadata(f).reuse()
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(&**self, f)
}
}
/// Implement [`SmartDisplay`] for unsigned integers.
macro_rules! impl_uint {
($($t:ty)*) => {$(
impl SmartDisplay for $t {
type Metadata = ();
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
let mut width = self.checked_ilog10().map_or(1, |n| n as usize + 1);
if f.sign_plus() || f.sign_minus() {
width += 1;
}
Metadata::new(width, self, ())
}
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
)*};
}
impl_uint![u8 u16 u32 u64 u128 usize];
/// Implement [`SmartDisplay`] for signed integers.
macro_rules! impl_int {
($($t:ty)*) => {$(
impl SmartDisplay for $t {
type Metadata = ();
fn metadata(&self, f: FormatterOptions) -> Metadata<'_, Self> {
let mut width = if f.sign_plus() || *self < 0 { 1 } else { 0 };
width += self.unsigned_abs().checked_ilog10().map_or(1, |n| n as usize + 1);
Metadata::new(width, self, ())
}
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
)*};
}
impl_int![i8 i16 i32 i64 i128 isize];
impl SmartDisplay for char {
type Metadata = ();
fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
let mut buf = [0; 4];
let c = self.encode_utf8(&mut buf);
Metadata::new(c.len(), self, ())
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"032c780eaf4ddfde703d5a6b260ad7bad35a5a1ee57a33cacf503f5e47dff6a9","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/convert.rs":"59566933f2977d62abbfe39b20be16a85df00db8627211471ccfe182dbbe684c","src/lib.rs":"18020c914b1cd561465e624ef3ea3eef980bd82bc93847e2543bce12da28b043","src/util.rs":"52c1fbf68b71c3582caf0d9a8255378c6c14a737e2df8d7e6d6603b0eb12ca06"},"package":"7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"}
{"files":{"Cargo.toml":"d90c41d20f37fc3dbc3d88f7715cacafb5aea973030f498e9b2833decdbe63f0","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/convert.rs":"354a1b05e8bb1e92eda5dcdecf33dc6cf2ce72b11115ae4cb0909dcd51d2b294","src/lib.rs":"461b752a45b0f819284e8d8e6b2f49d52b3b661026ab84ee64bf04f4daa0a2d2","src/util.rs":"52c1fbf68b71c3582caf0d9a8255378c6c14a737e2df8d7e6d6603b0eb12ca06"},"package":"ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"}

View file

@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.65.0"
rust-version = "1.67.0"
name = "time-core"
version = "0.1.1"
version = "0.1.2"
authors = [
"Jacob Pratt <open-source@jhpratt.dev>",
"Time contributors",
@ -29,4 +29,7 @@ categories = ["date-and-time"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/time-rs/time"
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
[dependencies]

View file

@ -1,88 +1,104 @@
#![allow(clippy::missing_docs_in_private_items)] // TODO temporary
//! Conversion between units of time.
macro_rules! declare_structs {
($($t:ident)*) => {$(
#[derive(Debug, Copy, Clone)]
use self::sealed::Per;
mod sealed {
/// A trait for defining the ratio of two units of time.
///
/// This trait is used to implement the `per` method on the various structs.
pub trait Per<T> {
/// The smallest unsigned integer type that can represent [`VALUE`](Self::VALUE).
type Output;
/// The number of one unit of time in the other.
const VALUE: Self::Output;
}
}
/// Declare and implement `Per` for all relevant types. Identity implementations are automatic.
macro_rules! impl_per {
($($t:ident ($str:literal) per {$(
$larger:ident : $output:ty = $value:expr
)*})*) => {$(
#[doc = concat!("A unit of time representing exactly one ", $str, ".")]
#[derive(Debug, Clone, Copy)]
pub struct $t;
impl $t {
pub const fn per<T>(self, _: T) -> <(Self, T) as Per>::Output
#[doc = concat!("Obtain the number of times `", stringify!($t), "` can fit into `T`.")]
#[doc = concat!("If `T` is smaller than `", stringify!($t), "`, the code will fail to")]
/// compile. The return type is the smallest unsigned integer type that can represent
/// the value.
///
/// Valid calls:
///
#[doc = concat!(" - `", stringify!($t), "::per(", stringify!($t), ")` (returns `u8`)")]
$(#[doc = concat!(" - `", stringify!($t), "::per(", stringify!($larger), ")` (returns `", stringify!($output), "`)")])*
pub const fn per<T>(_larger: T) -> <Self as Per<T>>::Output
where
(Self, T): Per,
Self: Per<T>,
T: Copy,
{
<(Self, T)>::VALUE
Self::VALUE
}
}
)*};
}
declare_structs! {
Nanosecond
Microsecond
Millisecond
Second
Minute
Hour
Day
Week
}
impl Per<$t> for $t {
type Output = u8;
mod sealed {
pub trait Sealed {}
}
pub trait Per: sealed::Sealed {
type Output;
const VALUE: Self::Output;
}
macro_rules! impl_per {
($($t:ty : $x:ident in $y:ident = $val:expr)*) => {$(
impl sealed::Sealed for ($x, $y) {}
impl Per for ($x, $y) {
type Output = $t;
const VALUE: $t = $val;
const VALUE: u8 = 1;
}
$(impl Per<$larger> for $t {
type Output = $output;
const VALUE: $output = $value;
})*
)*};
}
impl_per! {
u16: Nanosecond in Microsecond = 1_000
u32: Nanosecond in Millisecond = 1_000_000
u32: Nanosecond in Second = 1_000_000_000
u64: Nanosecond in Minute = 60_000_000_000
u64: Nanosecond in Hour = 3_600_000_000_000
u64: Nanosecond in Day = 86_400_000_000_000
u64: Nanosecond in Week = 604_800_000_000_000
u16: Microsecond in Millisecond = 1_000
u32: Microsecond in Second = 1_000_000
u32: Microsecond in Minute = 60_000_000
u32: Microsecond in Hour = 3_600_000_000
u64: Microsecond in Day = 86_400_000_000
u64: Microsecond in Week = 604_800_000_000
u16: Millisecond in Second = 1_000
u16: Millisecond in Minute = 60_000
u32: Millisecond in Hour = 3_600_000
u32: Millisecond in Day = 86_400_000
u32: Millisecond in Week = 604_800_000
u8: Second in Minute = 60
u16: Second in Hour = 3_600
u32: Second in Day = 86_400
u32: Second in Week = 604_800
u8: Minute in Hour = 60
u16: Minute in Day = 1_440
u16: Minute in Week = 10_080
u8: Hour in Day = 24
u8: Hour in Week = 168
u8: Day in Week = 7
Nanosecond ("nanosecond") per {
Microsecond: u16 = 1_000
Millisecond: u32 = 1_000_000
Second: u32 = 1_000_000_000
Minute: u64 = 60_000_000_000
Hour: u64 = 3_600_000_000_000
Day: u64 = 86_400_000_000_000
Week: u64 = 604_800_000_000_000
}
Microsecond ("microsecond") per {
Millisecond: u16 = 1_000
Second: u32 = 1_000_000
Minute: u32 = 60_000_000
Hour: u32 = 3_600_000_000
Day: u64 = 86_400_000_000
Week: u64 = 604_800_000_000
}
Millisecond ("millisecond") per {
Second: u16 = 1_000
Minute: u16 = 60_000
Hour: u32 = 3_600_000
Day: u32 = 86_400_000
Week: u32 = 604_800_000
}
Second ("second") per {
Minute: u8 = 60
Hour: u16 = 3_600
Day: u32 = 86_400
Week: u32 = 604_800
}
Minute ("minute") per {
Hour: u8 = 60
Day: u16 = 1_440
Week: u16 = 10_080
}
Hour ("hour") per {
Day: u8 = 24
Week: u8 = 168
}
Day ("day") per {
Week: u8 = 7
}
Week ("week") per {}
}

View file

@ -3,47 +3,6 @@
//! This crate is an implementation detail of `time` and should not be relied upon directly.
#![no_std]
#![deny(
anonymous_parameters,
clippy::all,
clippy::alloc_instead_of_core,
clippy::explicit_auto_deref,
clippy::obfuscated_if_else,
clippy::std_instead_of_core,
clippy::undocumented_unsafe_blocks,
illegal_floating_point_literal_pattern,
late_bound_lifetime_arguments,
path_statements,
patterns_in_fns_without_body,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_extern_crates,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links
)]
#![warn(
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::get_unwrap,
clippy::missing_docs_in_private_items,
clippy::nursery,
clippy::print_stdout,
clippy::todo,
clippy::unimplemented,
clippy::unnested_or_patterns,
clippy::unwrap_in_result,
clippy::unwrap_used,
clippy::use_debug,
deprecated_in_future,
missing_copy_implementations,
missing_debug_implementations,
unused_qualifications,
variant_size_differences
)]
#![allow(clippy::redundant_pub_crate)]
#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
#![doc(test(attr(deny(warnings))))]

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"97dbc36d7e8c8658e151c1cfe57397a116135a0d0efc97aacd339142da5d1c96","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/date.rs":"ffcd3d0998ec67abb43a3f8eccc6104172f5061b855312b89d53bb82fece2460","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"b3dea92631092068dd73e57e1cbf548f7ae85762826dcdea7fd6454bf357a50a","src/format_description/ast.rs":"8ba87e3249766b89c42b040f623d3134aeec46b78208fdfee825ed0eeeb4591a","src/format_description/format_item.rs":"03ff10699383e5ad08fe690199d45288f13363337abbc811a70b03a8b1703ab1","src/format_description/lexer.rs":"e7db7b6431f00c81b8d15a162088a1622ecd65bfb58d4e642c3c93a8dd5ae4ad","src/format_description/mod.rs":"f48c0ff590bc74529f06a98f60a6af5814bc30d1456bf0b81ac334c0b3f41bba","src/format_description/public/component.rs":"e2c2c8a189e2eb9f9354ff1d9d8edeafa34303e91dc58457df373e7e61c38b78","src/format_description/public/mod.rs":"5260592b310ea9e30808d30c92ea94c7bf1bdb171250a1342279e927d2528d73","src/format_description/public/modifier.rs":"37661e1f7cd9fd11a82f5a1ce6d5971686afa91e6feebc7b9d32df297e8b667f","src/helpers/mod.rs":"a8f8ed59a72b239d7a530357d212873f2e75ea924ec19a6d5d6e24a2baa8100c","src/helpers/string.rs":"3af2d0c701ca978c705922a272e76506dbdf0f376d44ed9ae7283086c67852ba","src/lib.rs":"200678edc14d5920abc0516717b8e010667e58da8bdc65c1cb583fdde0353089","src/offset.rs":"4b9c001a954c1f121a572f5675073f7a4e46d00cc9eb77736bfea2df94ffd05b","src/quote.rs":"634a12b95236e4ab2b8ab70a1a4a2629113c3ce3cf6defefc7ffeb81544c1d89","src/serde_format_description.rs":"db5fb2dc94e01c5114cab3484e68334516d53c4642f31dae0d66f1183253a17c","src/time.rs":"d762e8f22f749d9546d5d2a78b8a0380510be27b4cd3fed375695d7982d8396e","src/to_tokens.rs":"6636ea489c7484bad9b39f72d6956a04c95ce82d8462b12079cc03db778fd263"},"package":"96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"}
{"files":{"Cargo.toml":"3330436e81a4de8b20b9a2931f9e857b7974a8423462d928b04cff55ad531cff","LICENSE-Apache":"edd65bdd88957a205c47d53fa499eed8865a70320f0f03f6391668cb304ea376","LICENSE-MIT":"231c837c45eb53f108fb48929e488965bc4fcc14e9ea21d35f50e6b99d98685b","src/date.rs":"be197c8a2ed37e8b3123a798a91697b0e61cf9b60e7b1898a0e1b458fe8e3ef1","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"b3dea92631092068dd73e57e1cbf548f7ae85762826dcdea7fd6454bf357a50a","src/format_description/ast.rs":"697d5ce506b5386092d706bfe5bf4f81f50e1130796cb17c2fc61457fb165307","src/format_description/format_item.rs":"02d12976209c7af83c2aa4a3221a1a65420fae8c8ba12a28933fb738a2872ff9","src/format_description/lexer.rs":"e2c75f3dda5773a0c8301fdfc0d58a0b833923ba59ac04bcc49fd10aee20496c","src/format_description/mod.rs":"2109b77a8198769c6a6732a54233d7e0058bf4a6da724824103d107859795956","src/format_description/public/component.rs":"5d86912e247724957f7183d70745ced20a7408ed90c24bb47da73a0e26550899","src/format_description/public/mod.rs":"8030e767cb94d559dda2dddc72d42654a756362bd165e5c2cccf112f15d49610","src/format_description/public/modifier.rs":"e1d8fdababcaee2e181a7acb3a938baf309f5a0e2d3877585cf678fcc12f212a","src/helpers/mod.rs":"af47d6c053ffd1113788c5d7591d46fa7d879dc0c5cb2c6c02f9c05462499c4f","src/helpers/string.rs":"3af2d0c701ca978c705922a272e76506dbdf0f376d44ed9ae7283086c67852ba","src/lib.rs":"6ed2d4a41d15a1b5d9fef7d437a1520d967acbfbab98a88630062340f701ca54","src/offset.rs":"aed29d0da9fc65a7dc77314e0346dfdc6fdaf663f17adf9edf00933e8f8e605f","src/quote.rs":"d3003dafa3073825f188851a974846099681cc81145070affb033469cbc7bb31","src/serde_format_description.rs":"db5fb2dc94e01c5114cab3484e68334516d53c4642f31dae0d66f1183253a17c","src/time.rs":"299ddb54e44fb88e514592db5335f06352ebdd0dbf064752790657db85f4c13c","src/to_tokens.rs":"afb067f4d95d19c1b7a650cbcf60ae155b5a9619c89825867997f39ce163ac94"},"package":"3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"}

View file

@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.65.0"
rust-version = "1.67.0"
name = "time-macros"
version = "0.2.10"
version = "0.2.18"
authors = [
"Jacob Pratt <open-source@jhpratt.dev>",
"Time contributors",
@ -32,14 +32,104 @@ categories = ["date-and-time"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/time-rs/time"
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
[lib]
proc-macro = true
[dependencies.num-conv]
version = "0.1.0"
[dependencies.time-core]
version = "=0.1.1"
version = "=0.1.2"
[features]
formatting = []
large-dates = []
parsing = []
serde = []
[lints.clippy]
all = "warn"
alloc-instead-of-core = "deny"
dbg-macro = "warn"
decimal-literal-representation = "warn"
explicit-auto-deref = "warn"
get-unwrap = "warn"
manual-let-else = "warn"
missing-docs-in-private-items = "warn"
missing-enforced-import-renames = "warn"
nursery = "warn"
obfuscated-if-else = "warn"
print-stdout = "warn"
semicolon-outside-block = "warn"
std-instead-of-core = "deny"
todo = "warn"
undocumented-unsafe-blocks = "deny"
unimplemented = "warn"
uninlined-format-args = "warn"
unnested-or-patterns = "warn"
unwrap-in-result = "warn"
unwrap-used = "warn"
use-debug = "warn"
[lints.clippy.option-if-let-else]
level = "allow"
priority = 1
[lints.clippy.redundant-pub-crate]
level = "allow"
priority = 1
[lints.rust]
ambiguous-glob-reexports = "deny"
clashing-extern-declarations = "deny"
const-item-mutation = "deny"
deref-nullptr = "deny"
drop-bounds = "deny"
future-incompatible = "deny"
hidden-glob-reexports = "deny"
improper-ctypes = "deny"
improper-ctypes-definitions = "deny"
invalid-from-utf8 = "deny"
invalid-macro-export-arguments = "deny"
invalid-nan-comparisons = "deny"
invalid-reference-casting = "deny"
invalid-value = "deny"
keyword-idents = "warn"
let-underscore = "warn"
macro-use-extern-crate = "warn"
meta-variable-misuse = "warn"
missing-abi = "warn"
missing-copy-implementations = "warn"
missing-debug-implementations = "warn"
missing-docs = "warn"
named-arguments-used-positionally = "deny"
non-ascii-idents = "deny"
noop-method-call = "warn"
opaque-hidden-inferred-bound = "deny"
overlapping-range-endpoints = "deny"
single-use-lifetimes = "warn"
suspicious-double-ref-op = "deny"
temporary-cstring-as-ptr = "deny"
trivial-casts = "warn"
trivial-numeric-casts = "warn"
unconditional-recursion = "deny"
unnameable-test-items = "deny"
unreachable-pub = "warn"
unsafe-op-in-unsafe-fn = "deny"
unstable-syntax-pre-expansion = "deny"
unused = "warn"
unused-import-braces = "warn"
unused-lifetimes = "warn"
unused-qualifications = "warn"
variant-size-differences = "warn"
[lints.rust.unstable-name-collisions]
level = "warn"
priority = 1
[lints.rustdoc]
private-doc-tests = "warn"
unescaped-backticks = "warn"

View file

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Jacob Pratt et al.
Copyright 2024 Jacob Pratt et al.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
Copyright (c) 2022 Jacob Pratt et al.
Copyright (c) 2024 Jacob Pratt et al.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,5 +1,6 @@
use std::iter::Peekable;
use num_conv::Truncate;
use proc_macro::{token_stream, TokenTree};
use time_core::util::{days_in_year, weeks_in_year};
@ -93,7 +94,7 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date
span_end: Some(month_span),
});
}
let month = month as _;
let month = month.truncate();
if day == 0 || day > days_in_year_month(year, month) {
return Err(Error::InvalidComponent {
name: "day",
@ -127,10 +128,12 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date
impl ToTokenTree for Date {
fn into_token_tree(self) -> TokenTree {
quote_group! {{
const DATE: ::time::Date = ::time::Date::__from_ordinal_date_unchecked(
#(self.year),
#(self.ordinal),
);
const DATE: ::time::Date = unsafe {
::time::Date::__from_ordinal_date_unchecked(
#(self.year),
#(self.ordinal),
)
};
DATE
}}
}

View file

@ -1,4 +1,3 @@
use std::boxed::Box;
use std::iter;
use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused};

View file

@ -1,4 +1,3 @@
use std::boxed::Box;
use std::num::NonZeroU16;
use std::str::{self, FromStr};
@ -103,14 +102,9 @@ impl From<Item<'_>> for crate::format_description::public::OwnedFormatItem {
impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedFormatItem {
fn from(items: Box<[Item<'a>]>) -> Self {
let items = items.into_vec();
if items.len() == 1 {
if let Ok([item]) = <[_; 1]>::try_from(items) {
item.into()
} else {
bug!("the length was just checked to be 1")
}
} else {
Self::Compound(items.into_iter().map(Self::from).collect())
match <[_; 1]>::try_from(items) {
Ok([item]) => item.into(),
Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
}
}
}
@ -143,6 +137,7 @@ macro_rules! component_definition {
_component_span: Span,
) -> Result<Self, Error>
{
#[allow(unused_mut)]
let mut this = Self {
$($field: None),*
};
@ -212,6 +207,7 @@ component_definition! {
Day = "day" {
padding = "padding": Option<Padding> => padding,
},
End = "end" {},
Hour = "hour" {
padding = "padding": Option<Padding> => padding,
base = "repr": Option<HourBase> => is_12_hour_clock,

View file

@ -3,7 +3,7 @@ use core::iter;
use super::{Error, Location, Spanned, SpannedValue};
pub(super) struct Lexed<I: Iterator> {
iter: core::iter::Peekable<I>,
iter: iter::Peekable<I>,
}
impl<I: Iterator> Iterator for Lexed<I> {

View file

@ -1,7 +1,5 @@
//! Parser for format descriptions.
use std::vec::Vec;
macro_rules! version {
($range:expr) => {
$range.contains(&VERSION)
@ -17,7 +15,7 @@ pub(crate) fn parse_with_version(
version: Option<crate::FormatDescriptionVersion>,
s: &[u8],
proc_span: proc_macro::Span,
) -> Result<Vec<crate::format_description::public::OwnedFormatItem>, crate::Error> {
) -> Result<Vec<public::OwnedFormatItem>, crate::Error> {
match version {
Some(crate::FormatDescriptionVersion::V1) | None => parse::<1>(s, proc_span),
Some(crate::FormatDescriptionVersion::V2) => parse::<2>(s, proc_span),
@ -27,7 +25,7 @@ pub(crate) fn parse_with_version(
fn parse<const VERSION: u8>(
s: &[u8],
proc_span: proc_macro::Span,
) -> Result<Vec<crate::format_description::public::OwnedFormatItem>, crate::Error> {
) -> Result<Vec<public::OwnedFormatItem>, crate::Error> {
let mut lexed = lexer::lex::<VERSION>(s, proc_span);
let ast = ast::parse::<_, VERSION>(&mut lexed);
let format_items = format_item::parse(ast);

View file

@ -46,4 +46,5 @@ declare_component! {
OffsetSecond
Ignore
UnixTimestamp
End
}

View file

@ -19,12 +19,12 @@ impl ToTokenStream for OwnedFormatItem {
fn append_to(self, ts: &mut TokenStream) {
match self {
Self::Literal(bytes) => quote_append! { ts
::time::format_description::FormatItem::Literal {
::time::format_description::BorrowedFormatItem::Literal {
0: #(Literal::byte_string(bytes.as_ref()))
}
},
Self::Component(component) => quote_append! { ts
::time::format_description::FormatItem::Component { 0: #S(component) }
::time::format_description::BorrowedFormatItem::Component { 0: #S(component) }
},
Self::Compound(items) => {
let items = items
@ -33,11 +33,11 @@ impl ToTokenStream for OwnedFormatItem {
.map(|item| quote! { #S(item), })
.collect::<TokenStream>();
quote_append! { ts
::time::format_description::FormatItem::Compound { 0: &[#S(items)] }
::time::format_description::BorrowedFormatItem::Compound { 0: &[#S(items)] }
}
}
Self::Optional(item) => quote_append! {ts
::time::format_description::FormatItem::Optional { 0: &#S(*item) }
::time::format_description::BorrowedFormatItem::Optional { 0: &#S(*item) }
},
Self::First(items) => {
let items = items
@ -46,7 +46,7 @@ impl ToTokenStream for OwnedFormatItem {
.map(|item| quote! { #S(item), })
.collect::<TokenStream>();
quote_append! { ts
::time::format_description::FormatItem::First { 0: &[#S(items)] }
::time::format_description::BorrowedFormatItem::First { 0: &[#S(items)] }
}
}
}

View file

@ -10,18 +10,18 @@ macro_rules! to_tokens {
$struct_vis:vis struct $struct_name:ident {$(
$(#[$field_attr:meta])*
$field_vis:vis $field_name:ident : $field_ty:ty
),+ $(,)?}
),* $(,)?}
) => {
$(#[$struct_attr])*
$struct_vis struct $struct_name {$(
$(#[$field_attr])*
$field_vis $field_name: $field_ty
),+}
),*}
impl ToTokenTree for $struct_name {
fn into_token_tree(self) -> TokenTree {
let mut tokens = TokenStream::new();
let Self {$($field_name),+} = self;
let Self {$($field_name),*} = self;
quote_append! { tokens
let mut value = ::time::format_description::modifier::$struct_name::default();
@ -30,7 +30,7 @@ macro_rules! to_tokens {
quote_append!(tokens value.$field_name =);
$field_name.append_to(&mut tokens);
quote_append!(tokens ;);
)+
)*
quote_append!(tokens value);
proc_macro::TokenTree::Group(proc_macro::Group::new(
@ -245,3 +245,7 @@ to_tokens! {
pub(crate) sign_is_mandatory: bool,
}
}
to_tokens! {
pub(crate) struct End {}
}

View file

@ -4,6 +4,7 @@ mod string;
use std::iter::Peekable;
use std::str::FromStr;
use num_conv::prelude::*;
use proc_macro::{token_stream, Span, TokenTree};
use time_core::util::{days_in_year, is_leap_year};
@ -92,15 +93,17 @@ fn jan_weekday(year: i32, ordinal: i32) -> u8 {
}
let adj_year = year - 1;
((ordinal + adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
(ordinal + adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
+ div_floor!(adj_year, 400)
+ 6)
.rem_euclid(7)) as _
.rem_euclid(7)
.cast_unsigned()
.truncate()
}
pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month as usize - 1]
+ (month == 2 && is_leap_year(year)) as u8
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month.extend::<usize>() - 1]
+ u8::from(month == 2 && is_leap_year(year))
}
pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u16) {
@ -120,8 +123,9 @@ pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u1
}
pub(crate) fn ymd_to_yo(year: i32, month: u8, day: u8) -> (i32, u16) {
let ordinal = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334][month as usize - 1]
+ (month > 2 && is_leap_year(year)) as u16;
let ordinal = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
[month.extend::<usize>() - 1]
+ u16::from(month > 2 && is_leap_year(year));
(year, ordinal + u16::from(day))
}

View file

@ -1,37 +1,12 @@
#![deny(
anonymous_parameters,
clippy::all,
illegal_floating_point_literal_pattern,
late_bound_lifetime_arguments,
path_statements,
patterns_in_fns_without_body,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_code,
unused_extern_crates
)]
#![warn(
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::get_unwrap,
clippy::nursery,
clippy::print_stdout,
clippy::todo,
clippy::unimplemented,
clippy::unnested_or_patterns,
clippy::unwrap_used,
clippy::use_debug,
single_use_lifetimes,
unused_qualifications,
variant_size_differences
)]
#![allow(
clippy::missing_const_for_fn, // useless in proc macro
clippy::redundant_pub_crate, // suggests bad style
clippy::option_if_let_else, // suggests terrible code
clippy::missing_const_for_fn, // irrelevant for proc macros
clippy::missing_docs_in_private_items, // TODO remove
clippy::std_instead_of_core, // irrelevant for proc macros
clippy::std_instead_of_alloc, // irrelevant for proc macros
clippy::alloc_instead_of_core, // irrelevant for proc macros
missing_docs, // TODO remove
)]
#[allow(unused_macros)]
macro_rules! bug {
() => { compile_error!("provide an error message to help fix a possible bug") };
@ -93,6 +68,7 @@ enum FormatDescriptionVersion {
#[cfg(any(feature = "formatting", feature = "parsing"))]
enum VersionOrModuleName {
Version(FormatDescriptionVersion),
#[cfg_attr(not(feature = "serde"), allow(dead_code))]
ModuleName(Ident),
}
@ -175,7 +151,7 @@ pub fn format_description(input: TokenStream) -> TokenStream {
let items = format_description::parse_with_version(version, &string, span)?;
Ok(quote! {{
const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S(
const DESCRIPTION: &[::time::format_description::BorrowedFormatItem<'_>] = &[#S(
items
.into_iter()
.map(|item| quote! { #S(item), })
@ -236,7 +212,8 @@ pub fn serde_format_description(input: TokenStream) -> TokenStream {
let items: TokenStream =
items.into_iter().map(|item| quote! { #S(item), }).collect();
let items = quote! {
const ITEMS: &[::time::format_description::FormatItem<'_>] = &[#S(items)];
const ITEMS: &[::time::format_description::BorrowedFormatItem<'_>]
= &[#S(items)];
ITEMS
};

View file

@ -1,5 +1,6 @@
use std::iter::Peekable;
use num_conv::prelude::*;
use proc_macro::{token_stream, Span, TokenTree};
use time_core::convert::*;
@ -52,21 +53,21 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offs
}
}
if hours >= 24 {
if hours > 25 {
Err(Error::InvalidComponent {
name: "hour",
value: hours.to_string(),
span_start: Some(hours_span),
span_end: Some(hours_span),
})
} else if minutes >= Minute.per(Hour) as _ {
} else if minutes >= Minute::per(Hour).cast_signed() {
Err(Error::InvalidComponent {
name: "minute",
value: minutes.to_string(),
span_start: Some(minutes_span),
span_end: Some(minutes_span),
})
} else if seconds >= Second.per(Minute) as _ {
} else if seconds >= Second::per(Minute).cast_signed() {
Err(Error::InvalidComponent {
name: "second",
value: seconds.to_string(),
@ -85,11 +86,13 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offs
impl ToTokenTree for Offset {
fn into_token_tree(self) -> TokenTree {
quote_group! {{
const OFFSET: ::time::UtcOffset = ::time::UtcOffset::__from_hms_unchecked(
#(self.hours),
#(self.minutes),
#(self.seconds),
);
const OFFSET: ::time::UtcOffset = unsafe {
::time::UtcOffset::__from_hms_unchecked(
#(self.hours),
#(self.minutes),
#(self.seconds),
)
};
OFFSET
}}
}

View file

@ -45,20 +45,19 @@ macro_rules! sym {
};
}
#[allow(unused_macro_rules)] // Varies by feature flag combination.
macro_rules! quote_inner {
// Base case
($ts:ident) => {};
// Single or double symbols
($ts:ident :: $($tail:tt)*) => { sym!($ts ':' ':'); quote_inner!($ts $($tail)*); };
($ts:ident .. $($tail:tt)*) => { sym!($ts '.' '.'); quote_inner!($ts $($tail)*); };
($ts:ident : $($tail:tt)*) => { sym!($ts ':'); quote_inner!($ts $($tail)*); };
($ts:ident = $($tail:tt)*) => { sym!($ts '='); quote_inner!($ts $($tail)*); };
($ts:ident ; $($tail:tt)*) => { sym!($ts ';'); quote_inner!($ts $($tail)*); };
($ts:ident , $($tail:tt)*) => { sym!($ts ','); quote_inner!($ts $($tail)*); };
($ts:ident . $($tail:tt)*) => { sym!($ts '.'); quote_inner!($ts $($tail)*); };
($ts:ident & $($tail:tt)*) => { sym!($ts '&'); quote_inner!($ts $($tail)*); };
($ts:ident << $($tail:tt)*) => { sym!($ts '<' '<'); quote_inner!($ts $($tail)*); };
($ts:ident < $($tail:tt)*) => { sym!($ts '<'); quote_inner!($ts $($tail)*); };
($ts:ident >> $($tail:tt)*) => { sym!($ts '>' '>'); quote_inner!($ts $($tail)*); };
($ts:ident > $($tail:tt)*) => { sym!($ts '>'); quote_inner!($ts $($tail)*); };

View file

@ -73,21 +73,21 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time
(hour, Period::Pm) => hour + 12,
};
if hour >= Hour.per(Day) {
if hour >= Hour::per(Day) {
Err(Error::InvalidComponent {
name: "hour",
value: hour.to_string(),
span_start: Some(hour_span),
span_end: Some(period_span.unwrap_or(hour_span)),
})
} else if minute >= Minute.per(Hour) {
} else if minute >= Minute::per(Hour) {
Err(Error::InvalidComponent {
name: "minute",
value: minute.to_string(),
span_start: Some(minute_span),
span_end: Some(minute_span),
})
} else if second >= Second.per(Minute) as _ {
} else if second >= Second::per(Minute) as _ {
Err(Error::InvalidComponent {
name: "second",
value: second.to_string(),
@ -99,7 +99,7 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time
hour,
minute,
second: second.trunc() as _,
nanosecond: (second.fract() * Nanosecond.per(Second) as f64).round() as _,
nanosecond: (second.fract() * Nanosecond::per(Second) as f64).round() as _,
})
}
}
@ -107,12 +107,14 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time
impl ToTokenTree for Time {
fn into_token_tree(self) -> TokenTree {
quote_group! {{
const TIME: ::time::Time = ::time::Time::__from_hms_nanos_unchecked(
#(self.hour),
#(self.minute),
#(self.second),
#(self.nanosecond),
);
const TIME: ::time::Time = unsafe {
::time::Time::__from_hms_nanos_unchecked(
#(self.hour),
#(self.minute),
#(self.second),
#(self.nanosecond),
)
};
TIME
}}
}

View file

@ -2,6 +2,7 @@ use std::num::NonZeroU16;
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
/// Turn a type into a [`TokenStream`].
pub(crate) trait ToTokenStream: Sized {
fn append_to(self, ts: &mut TokenStream);
}

File diff suppressed because one or more lines are too long

View file

@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.65.0"
rust-version = "1.67.0"
name = "time"
version = "0.3.23"
version = "0.3.36"
authors = [
"Jacob Pratt <open-source@jhpratt.dev>",
"Time contributors",
@ -46,6 +46,7 @@ all-features = true
rustdoc-args = [
"--cfg",
"__time_03_docs",
"--generate-link-to-definition",
]
targets = ["x86_64-unknown-linux-gnu"]
@ -61,10 +62,22 @@ name = "benchmarks"
path = "../benchmarks/main.rs"
harness = false
[dependencies.deranged]
version = "0.3.9"
features = ["powerfmt"]
default-features = false
[dependencies.itoa]
version = "1.0.1"
optional = true
[dependencies.num-conv]
version = "0.1.0"
[dependencies.powerfmt]
version = "0.2.0"
default-features = false
[dependencies.quickcheck]
version = "1.0.3"
optional = true
@ -76,17 +89,20 @@ optional = true
default-features = false
[dependencies.serde]
version = "1.0.126"
version = "1.0.184"
optional = true
default-features = false
[dependencies.time-core]
version = "=0.1.1"
version = "=0.1.2"
[dependencies.time-macros]
version = "=0.2.10"
version = "=0.2.18"
optional = true
[dev-dependencies.num-conv]
version = "0.1.0"
[dev-dependencies.quickcheck_macros]
version = "1.0.0"
@ -94,8 +110,15 @@ version = "1.0.0"
version = "0.8.4"
default-features = false
[dev-dependencies.rstest]
version = "0.18.2"
default-features = false
[dev-dependencies.rstest_reuse]
version = "0.6.0"
[dev-dependencies.serde]
version = "1.0.126"
version = "1.0.184"
features = ["derive"]
default-features = false
@ -106,7 +129,7 @@ version = "1.0.68"
version = "1.0.126"
[dev-dependencies.time-macros]
version = "=0.2.10"
version = "=0.2.18"
[features]
alloc = ["serde?/alloc"]
@ -127,11 +150,16 @@ parsing = ["time-macros?/parsing"]
quickcheck = [
"dep:quickcheck",
"alloc",
"deranged/quickcheck",
]
rand = [
"dep:rand",
"deranged/rand",
]
rand = ["dep:rand"]
serde = [
"dep:serde",
"time-macros?/serde",
"deranged/serde",
]
serde-human-readable = [
"serde",
@ -143,7 +171,10 @@ serde-well-known = [
"formatting",
"parsing",
]
std = ["alloc"]
std = [
"alloc",
"deranged/std",
]
wasm-bindgen = ["dep:js-sys"]
[target."cfg(__ui_tests)".dev-dependencies.trybuild]
@ -154,7 +185,7 @@ version = "0.3.58"
optional = true
[target."cfg(bench)".dev-dependencies.criterion]
version = "0.4.0"
version = "0.5.1"
default-features = false
[target."cfg(target_family = \"unix\")".dependencies.libc]
@ -164,3 +195,87 @@ optional = true
[target."cfg(target_family = \"unix\")".dependencies.num_threads]
version = "0.1.2"
optional = true
[lints.clippy]
all = "warn"
alloc-instead-of-core = "deny"
dbg-macro = "warn"
decimal-literal-representation = "warn"
explicit-auto-deref = "warn"
get-unwrap = "warn"
manual-let-else = "warn"
missing-docs-in-private-items = "warn"
missing-enforced-import-renames = "warn"
nursery = "warn"
obfuscated-if-else = "warn"
print-stdout = "warn"
semicolon-outside-block = "warn"
std-instead-of-core = "deny"
todo = "warn"
undocumented-unsafe-blocks = "deny"
unimplemented = "warn"
uninlined-format-args = "warn"
unnested-or-patterns = "warn"
unwrap-in-result = "warn"
unwrap-used = "warn"
use-debug = "warn"
[lints.clippy.option-if-let-else]
level = "allow"
priority = 1
[lints.clippy.redundant-pub-crate]
level = "allow"
priority = 1
[lints.rust]
ambiguous-glob-reexports = "deny"
clashing-extern-declarations = "deny"
const-item-mutation = "deny"
deref-nullptr = "deny"
drop-bounds = "deny"
future-incompatible = "deny"
hidden-glob-reexports = "deny"
improper-ctypes = "deny"
improper-ctypes-definitions = "deny"
invalid-from-utf8 = "deny"
invalid-macro-export-arguments = "deny"
invalid-nan-comparisons = "deny"
invalid-reference-casting = "deny"
invalid-value = "deny"
keyword-idents = "warn"
let-underscore = "warn"
macro-use-extern-crate = "warn"
meta-variable-misuse = "warn"
missing-abi = "warn"
missing-copy-implementations = "warn"
missing-debug-implementations = "warn"
missing-docs = "warn"
named-arguments-used-positionally = "deny"
non-ascii-idents = "deny"
noop-method-call = "warn"
opaque-hidden-inferred-bound = "deny"
overlapping-range-endpoints = "deny"
single-use-lifetimes = "warn"
suspicious-double-ref-op = "deny"
temporary-cstring-as-ptr = "deny"
trivial-casts = "warn"
trivial-numeric-casts = "warn"
unconditional-recursion = "deny"
unnameable-test-items = "deny"
unreachable-pub = "warn"
unsafe-op-in-unsafe-fn = "deny"
unstable-syntax-pre-expansion = "deny"
unused = "warn"
unused-import-braces = "warn"
unused-lifetimes = "warn"
unused-qualifications = "warn"
variant-size-differences = "warn"
[lints.rust.unstable-name-collisions]
level = "warn"
priority = 1
[lints.rustdoc]
private-doc-tests = "warn"
unescaped-backticks = "warn"

View file

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Jacob Pratt et al.
Copyright 2024 Jacob Pratt et al.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -1,4 +1,4 @@
Copyright (c) 2022 Jacob Pratt et al.
Copyright (c) 2024 Jacob Pratt et al.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,6 +1,6 @@
# time
[![minimum rustc: 1.65](https://img.shields.io/badge/minimum%20rustc-1.65-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
[![minimum rustc: 1.67](https://img.shields.io/badge/minimum%20rustc-1.67-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
[![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time)
[![build status](https://img.shields.io/github/actions/workflow/status/time-rs/time/build.yaml?branch=main&style=flat-square)](https://github.com/time-rs/time/actions)
[![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time)

View file

@ -1,19 +1,34 @@
//! The [`Date`] struct and its associated `impl`s.
use core::fmt;
#[cfg(feature = "formatting")]
use alloc::string::String;
use core::num::NonZeroI32;
use core::ops::{Add, Sub};
use core::time::Duration as StdDuration;
use core::{cmp, fmt};
#[cfg(feature = "formatting")]
use std::io;
use deranged::RangedI32;
use num_conv::prelude::*;
use powerfmt::ext::FormatterExt;
use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};
use crate::convert::*;
use crate::ext::DigitCount;
#[cfg(feature = "formatting")]
use crate::formatting::Formattable;
use crate::internal_macros::{
cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, impl_add_assign,
impl_sub_assign,
};
#[cfg(feature = "parsing")]
use crate::parsing::Parsable;
use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year};
use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday};
type Year = RangedI32<MIN_YEAR, MAX_YEAR>;
/// The minimum valid year.
pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") {
-999_999
@ -39,32 +54,43 @@ pub struct Date {
// | 2 bits | 21 bits | 9 bits |
// | unassigned | year | ordinal |
// The year is 15 bits when `large-dates` is not enabled.
value: i32,
value: NonZeroI32,
}
impl Date {
/// The minimum valid `Date`.
///
/// The value of this may vary depending on the feature flags enabled.
pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1);
// Safety: `ordinal` is not zero.
#[allow(clippy::undocumented_unsafe_blocks)]
pub const MIN: Self = unsafe { Self::__from_ordinal_date_unchecked(MIN_YEAR, 1) };
/// The maximum valid `Date`.
///
/// The value of this may vary depending on the feature flags enabled.
pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR));
// Safety: `ordinal` is not zero.
#[allow(clippy::undocumented_unsafe_blocks)]
pub const MAX: Self =
unsafe { Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)) };
// region: constructors
/// Construct a `Date` from the year and ordinal values, the validity of which must be
/// guaranteed by the caller.
///
/// # Safety
///
/// `ordinal` must not be zero. `year` should be in the range `MIN_YEAR..=MAX_YEAR`, but this
/// is not a safety invariant.
#[doc(hidden)]
pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
pub const unsafe fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
debug_assert!(year >= MIN_YEAR);
debug_assert!(year <= MAX_YEAR);
debug_assert!(ordinal != 0);
debug_assert!(ordinal <= days_in_year(year));
Self {
value: (year << 9) | ordinal as i32,
// Safety: The caller must guarantee that `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked((year << 9) | ordinal as i32) },
}
}
@ -91,14 +117,29 @@ impl Date {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335],
];
ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month));
ensure_ranged!(Year: year);
match day {
1..=28 => {}
29..=31 if day <= days_in_year_month(year, month) => {}
_ => {
return Err(error::ComponentRange {
name: "day",
minimum: 1,
maximum: days_in_year_month(year, month) as _,
value: day as _,
conditional_range: true,
});
}
}
Ok(Self::__from_ordinal_date_unchecked(
year,
DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ day as u16,
))
// Safety: `ordinal` is not zero.
Ok(unsafe {
Self::__from_ordinal_date_unchecked(
year,
DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ day as u16,
)
})
}
/// Attempt to create a `Date` from the year and ordinal day number.
@ -114,9 +155,23 @@ impl Date {
/// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year.
/// ```
pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> {
ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year));
Ok(Self::__from_ordinal_date_unchecked(year, ordinal))
ensure_ranged!(Year: year);
match ordinal {
1..=365 => {}
366 if is_leap_year(year) => {}
_ => {
return Err(error::ComponentRange {
name: "ordinal",
minimum: 1,
maximum: days_in_year(year) as _,
value: ordinal as _,
conditional_range: true,
});
}
}
// Safety: `ordinal` is not zero.
Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) })
}
/// Attempt to create a `Date` from the ISO year, week, and weekday.
@ -137,8 +192,20 @@ impl Date {
week: u8,
weekday: Weekday,
) -> Result<Self, error::ComponentRange> {
ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year));
ensure_ranged!(Year: year);
match week {
1..=52 => {}
53 if week <= weeks_in_year(year) => {}
_ => {
return Err(error::ComponentRange {
name: "week",
minimum: 1,
maximum: weeks_in_year(year) as _,
value: week as _,
conditional_range: true,
});
}
}
let adj_year = year - 1;
let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
@ -155,14 +222,21 @@ impl Date {
let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4;
Ok(if ordinal <= 0 {
Self::__from_ordinal_date_unchecked(
year - 1,
(ordinal as u16).wrapping_add(days_in_year(year - 1)),
)
// Safety: `ordinal` is not zero.
unsafe {
Self::__from_ordinal_date_unchecked(
year - 1,
(ordinal as u16).wrapping_add(days_in_year(year - 1)),
)
}
} else if ordinal > days_in_year(year) as i16 {
Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
// Safety: `ordinal` is not zero.
unsafe {
Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
}
} else {
Self::__from_ordinal_date_unchecked(year, ordinal as _)
// Safety: `ordinal` is not zero.
unsafe { Self::__from_ordinal_date_unchecked(year, ordinal as _) }
})
}
@ -181,9 +255,8 @@ impl Date {
/// ```
#[doc(alias = "from_julian_date")]
pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> {
ensure_value_in_range!(
julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day()
);
type JulianDay = RangedI32<{ Date::MIN.to_julian_day() }, { Date::MAX.to_julian_day() }>;
ensure_ranged!(JulianDay: julian_day);
Ok(Self::from_julian_day_unchecked(julian_day))
}
@ -223,7 +296,8 @@ impl Date {
cascade!(ordinal in 1..366 => year);
}
Self::__from_ordinal_date_unchecked(year, ordinal)
// Safety: `ordinal` is not zero.
unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }
}
// endregion constructors
@ -237,7 +311,7 @@ impl Date {
/// assert_eq!(date!(2020 - 01 - 01).year(), 2020);
/// ```
pub const fn year(self) -> i32 {
self.value >> 9
self.value.get() >> 9
}
/// Get the month.
@ -316,7 +390,7 @@ impl Date {
/// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365);
/// ```
pub const fn ordinal(self) -> u16 {
(self.value & 0x1FF) as _
(self.value.get() & 0x1FF) as _
}
/// Get the ISO 8601 year and week number.
@ -483,14 +557,16 @@ impl Date {
/// ```
pub const fn next_day(self) -> Option<Self> {
if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) {
if self.value == Self::MAX.value {
if self.value.get() == Self::MAX.value.get() {
None
} else {
Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1))
// Safety: `ordinal` is not zero.
unsafe { Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) }
}
} else {
Some(Self {
value: self.value + 1,
// Safety: `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked(self.value.get() + 1) },
})
}
}
@ -517,15 +593,16 @@ impl Date {
pub const fn previous_day(self) -> Option<Self> {
if self.ordinal() != 1 {
Some(Self {
value: self.value - 1,
// Safety: `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked(self.value.get() - 1) },
})
} else if self.value == Self::MIN.value {
} else if self.value.get() == Self::MIN.value.get() {
None
} else {
Some(Self::__from_ordinal_date_unchecked(
self.year() - 1,
days_in_year(self.year() - 1),
))
// Safety: `ordinal` is not zero.
Some(unsafe {
Self::__from_ordinal_date_unchecked(self.year() - 1, days_in_year(self.year() - 1))
})
}
}
@ -697,6 +774,49 @@ impl Date {
}
}
/// Computes `self + duration`, returning `None` if an overflow occurred.
///
/// ```rust
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MAX.checked_add_std(1.std_days()), None);
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(2.std_days()),
/// Some(date!(2021 - 01 - 02))
/// );
/// ```
///
/// # Note
///
/// This function only takes whole days into account.
///
/// ```rust
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MAX.checked_add_std(23.std_hours()), Some(Date::MAX));
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(23.std_hours()),
/// Some(date!(2020 - 12 - 31))
/// );
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(47.std_hours()),
/// Some(date!(2021 - 01 - 01))
/// );
/// ```
pub const fn checked_add_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second::per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}
let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _));
if let Ok(date) = Self::from_julian_day(julian_day) {
Some(date)
} else {
None
}
}
/// Computes `self - duration`, returning `None` if an overflow occurred.
///
/// ```
@ -742,6 +862,49 @@ impl Date {
}
}
/// Computes `self - duration`, returning `None` if an overflow occurred.
///
/// ```
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MIN.checked_sub_std(1.std_days()), None);
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(2.std_days()),
/// Some(date!(2020 - 12 - 29))
/// );
/// ```
///
/// # Note
///
/// This function only takes whole days into account.
///
/// ```
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MIN.checked_sub_std(23.std_hours()), Some(Date::MIN));
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(23.std_hours()),
/// Some(date!(2020 - 12 - 31))
/// );
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(47.std_hours()),
/// Some(date!(2020 - 12 - 30))
/// );
/// ```
pub const fn checked_sub_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second::per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}
let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _));
if let Ok(date) = Self::from_julian_day(julian_day) {
Some(date)
} else {
None
}
}
/// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
/// Returns `None` if an overflow occurred.
pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option<Self> {
@ -787,12 +950,8 @@ impl Date {
return None;
}
let next_occ = self.checked_next_occurrence(weekday);
if let Some(val) = next_occ {
val.checked_add(Duration::weeks(n as i64 - 1))
} else {
None
}
const_try_opt!(self.checked_next_occurrence(weekday))
.checked_add(Duration::weeks(n as i64 - 1))
}
/// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`.
@ -802,12 +961,8 @@ impl Date {
return None;
}
let next_occ = self.checked_prev_occurrence(weekday);
if let Some(val) = next_occ {
val.checked_sub(Duration::weeks(n as i64 - 1))
} else {
None
}
const_try_opt!(self.checked_prev_occurrence(weekday))
.checked_sub(Duration::weeks(n as i64 - 1))
}
// endregion: checked arithmetic
@ -907,17 +1062,21 @@ impl Date {
/// ```
#[must_use = "This method does not mutate the original `Date`."]
pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
ensure_ranged!(Year: year);
let ordinal = self.ordinal();
// Dates in January and February are unaffected by leap years.
if ordinal <= 59 {
return Ok(Self::__from_ordinal_date_unchecked(year, ordinal));
// Safety: `ordinal` is not zero.
return Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) });
}
match (is_leap_year(self.year()), is_leap_year(year)) {
(false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)),
(false, false) | (true, true) => {
// Safety: `ordinal` is not zero.
Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) })
}
// February 29 does not exist in common years.
(true, false) if ordinal == 60 => Err(error::ComponentRange {
name: "day",
@ -928,10 +1087,12 @@ impl Date {
}),
// We're going from a common year to a leap year. Shift dates in March and later by
// one day.
(false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)),
// Safety: `ordinal` is not zero.
(false, true) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal + 1) }),
// We're going from a leap year to a common year. Shift dates in January and
// February by one day.
(true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)),
// Safety: `ordinal` is not zero.
(true, false) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal - 1) }),
}
}
@ -969,17 +1130,55 @@ impl Date {
/// ```
#[must_use = "This method does not mutate the original `Date`."]
pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
// Days 1-28 are present in every month, so we can skip checking.
if day == 0 || day >= 29 {
ensure_value_in_range!(
day conditionally in 1 => days_in_year_month(self.year(), self.month())
);
match day {
1..=28 => {}
29..=31 if day <= days_in_year_month(self.year(), self.month()) => {}
_ => {
return Err(error::ComponentRange {
name: "day",
minimum: 1,
maximum: days_in_year_month(self.year(), self.month()) as _,
value: day as _,
conditional_range: true,
});
}
}
Ok(Self::__from_ordinal_date_unchecked(
self.year(),
(self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
))
// Safety: `ordinal` is not zero.
Ok(unsafe {
Self::__from_ordinal_date_unchecked(
self.year(),
(self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
)
})
}
/// Replace the day of the year.
///
/// ```rust
/// # use time_macros::date;
/// assert_eq!(date!(2022 - 049).replace_ordinal(1), Ok(date!(2022 - 001)));
/// assert!(date!(2022 - 049).replace_ordinal(0).is_err()); // 0 isn't a valid ordinal
/// assert!(date!(2022 - 049).replace_ordinal(366).is_err()); // 2022 isn't a leap year
/// ````
#[must_use = "This method does not mutate the original `Date`."]
pub const fn replace_ordinal(self, ordinal: u16) -> Result<Self, error::ComponentRange> {
match ordinal {
1..=365 => {}
366 if is_leap_year(self.year()) => {}
_ => {
return Err(error::ComponentRange {
name: "ordinal",
minimum: 1,
maximum: days_in_year(self.year()) as _,
value: ordinal as _,
conditional_range: true,
});
}
}
// Safety: `ordinal` is in range.
Ok(unsafe { Self::__from_ordinal_date_unchecked(self.year(), ordinal) })
}
// endregion replacement
}
@ -1138,29 +1337,92 @@ impl Date {
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 {
write!(
f,
"{:+}-{:02}-{:02}",
self.year(),
self.month() as u8,
self.day()
mod private {
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct DateMetadata {
/// The width of the year component, including the sign.
pub(super) year_width: u8,
/// Whether the sign should be displayed.
pub(super) display_sign: bool,
pub(super) year: i32,
pub(super) month: u8,
pub(super) day: u8,
}
}
use private::DateMetadata;
impl SmartDisplay for Date {
type Metadata = DateMetadata;
fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
let (year, month, day) = self.to_calendar_date();
// There is a minimum of four digits for any year.
let mut year_width = cmp::max(year.unsigned_abs().num_digits(), 4);
let display_sign = if !(0..10_000).contains(&year) {
// An extra character is required for the sign.
year_width += 1;
true
} else {
false
};
let formatted_width = year_width.extend::<usize>()
+ smart_display::padded_width_of!(
"-",
u8::from(month) => width(2),
"-",
day => width(2),
);
Metadata::new(
formatted_width,
self,
DateMetadata {
year_width,
display_sign,
year,
month: u8::from(month),
day,
},
)
}
fn fmt_with_metadata(
&self,
f: &mut fmt::Formatter<'_>,
metadata: Metadata<Self>,
) -> fmt::Result {
let DateMetadata {
year_width,
display_sign,
year,
month,
day,
} = *metadata;
let year_width = year_width.extend();
if display_sign {
f.pad_with_width(
metadata.unpadded_width(),
format_args!("{year:+0year_width$}-{month:02}-{day:02}"),
)
} else {
write!(
f,
"{:0width$}-{:02}-{:02}",
self.year(),
self.month() as u8,
self.day(),
width = 4 + (self.year() < 0) as usize
f.pad_with_width(
metadata.unpadded_width(),
format_args!("{year:0year_width$}-{month:02}-{day:02}"),
)
}
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self, f)
}
}
impl fmt::Debug for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Display::fmt(self, f)
@ -1172,6 +1434,9 @@ impl fmt::Debug for Date {
impl Add<Duration> for Date {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, duration: Duration) -> Self::Output {
self.checked_add(duration)
.expect("overflow adding duration to date")
@ -1181,11 +1446,12 @@ impl Add<Duration> for Date {
impl Add<StdDuration> for Date {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, duration: StdDuration) -> Self::Output {
Self::from_julian_day(
self.to_julian_day() + (duration.as_secs() / Second.per(Day) as u64) as i32,
)
.expect("overflow adding duration to date")
self.checked_add_std(duration)
.expect("overflow adding duration to date")
}
}
@ -1194,6 +1460,9 @@ impl_add_assign!(Date: Duration, StdDuration);
impl Sub<Duration> for Date {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, duration: Duration) -> Self::Output {
self.checked_sub(duration)
.expect("overflow subtracting duration from date")
@ -1203,11 +1472,12 @@ impl Sub<Duration> for Date {
impl Sub<StdDuration> for Date {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, duration: StdDuration) -> Self::Output {
Self::from_julian_day(
self.to_julian_day() - (duration.as_secs() / Second.per(Day) as u64) as i32,
)
.expect("overflow subtracting duration from date")
self.checked_sub_std(duration)
.expect("overflow subtracting duration from date")
}
}
@ -1217,7 +1487,7 @@ impl Sub for Date {
type Output = Duration;
fn sub(self, other: Self) -> Self::Output {
Duration::days((self.to_julian_day() - other.to_julian_day()) as _)
Duration::days((self.to_julian_day() - other.to_julian_day()).extend())
}
}
// endregion trait impls

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,16 @@ use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use core::time::Duration as StdDuration;
use deranged::RangedI32;
use num_conv::prelude::*;
use crate::convert::*;
use crate::error;
use crate::internal_macros::{
const_try_opt, expect_opt, impl_add_assign, impl_div_assign, impl_mul_assign, impl_sub_assign,
};
#[cfg(feature = "std")]
#[allow(deprecated)]
use crate::Instant;
/// By explicitly inserting this enum where padding is expected, the compiler is able to better
@ -20,11 +27,9 @@ pub(crate) enum Padding {
Optimize,
}
impl Default for Padding {
fn default() -> Self {
Self::Optimize
}
}
/// The type of the `nanosecond` field of `Duration`.
type Nanoseconds =
RangedI32<{ -(Nanosecond::per(Second) as i32 - 1) }, { Nanosecond::per(Second) as i32 - 1 }>;
/// A span of time with nanosecond precision.
///
@ -32,12 +37,13 @@ impl Default for Padding {
/// nanoseconds.
///
/// This implementation allows for negative durations, unlike [`core::time::Duration`].
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Duration {
/// Number of whole seconds.
seconds: i64,
/// Number of nanoseconds within the second. The sign always matches the `seconds` field.
nanoseconds: i32, // always -10^9 < nanoseconds < 10^9
// Sign must match that of `seconds` (though this is not a safety requirement).
nanoseconds: Nanoseconds,
#[allow(clippy::missing_docs_in_private_items)]
padding: Padding,
}
@ -51,10 +57,22 @@ impl fmt::Debug for Duration {
}
}
/// This is adapted from the `std` implementation, which uses mostly bit
impl Default for Duration {
fn default() -> Self {
Self {
seconds: 0,
nanoseconds: Nanoseconds::new_static::<0>(),
padding: Padding::Optimize,
}
}
}
/// This is adapted from the [`std` implementation][std], which uses mostly bit
/// operations to ensure the highest precision:
/// https://github.com/rust-lang/rust/blob/3a37c2f0523c87147b64f1b8099fc9df22e8c53e/library/core/src/time.rs#L1262-L1340
///
/// Changes from `std` are marked and explained below.
///
/// [std]: https://github.com/rust-lang/rust/blob/3a37c2f0523c87147b64f1b8099fc9df22e8c53e/library/core/src/time.rs#L1262-L1340
#[rustfmt::skip] // Skip `rustfmt` because it reformats the arguments of the macro weirdly.
macro_rules! try_from_secs {
(
@ -87,7 +105,7 @@ macro_rules! try_from_secs {
// the input is less than 1 second
let t = <$double_ty>::from(mant) << ($offset + exp);
let nanos_offset = $mant_bits + $offset;
let nanos_tmp = u128::from(Nanosecond.per(Second)) * u128::from(t);
let nanos_tmp = u128::from(Nanosecond::per(Second)) * u128::from(t);
let nanos = (nanos_tmp >> nanos_offset) as u32;
let rem_mask = (1 << nanos_offset) - 1;
@ -101,7 +119,7 @@ macro_rules! try_from_secs {
// f32 does not have enough precision to trigger the second branch
// since it can not represent numbers between 0.999_999_940_395 and 1.0.
let nanos = nanos + add_ns as u32;
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
if ($mant_bits == 23) || (nanos != Nanosecond::per(Second)) {
(0, nanos)
} else {
(1, 0)
@ -110,7 +128,7 @@ macro_rules! try_from_secs {
let secs = u64::from(mant >> ($mant_bits - exp));
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
let nanos_offset = $mant_bits;
let nanos_tmp = <$double_ty>::from(Nanosecond.per(Second)) * t;
let nanos_tmp = <$double_ty>::from(Nanosecond::per(Second)) * t;
let nanos = (nanos_tmp >> nanos_offset) as u32;
let rem_mask = (1 << nanos_offset) - 1;
@ -126,7 +144,7 @@ macro_rules! try_from_secs {
// and 2.0. Bigger values result in even smaller precision of the
// fractional part.
let nanos = nanos + add_ns as u32;
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
if ($mant_bits == 23) || (nanos != Nanosecond::per(Second)) {
(secs, nanos)
} else {
(secs + 1, 0)
@ -144,7 +162,7 @@ macro_rules! try_from_secs {
// following numbers -128..=127. The check above (exp < 63)
// doesn't cover i64::MIN as that is -2^63, so we have this
// additional case to handle the asymmetry of iN::MIN.
break 'value Self::new_unchecked(i64::MIN, 0);
break 'value Self::new_ranged_unchecked(i64::MIN, Nanoseconds::new_static::<0>());
} else if $secs.is_nan() {
// Change from std: std doesn't differentiate between the error
// cases.
@ -164,7 +182,8 @@ macro_rules! try_from_secs {
let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64);
#[allow(trivial_numeric_casts)]
let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32);
Self::new_unchecked(secs_signed, nanos_signed)
// Safety: `nanos_signed` is in range.
unsafe { Self::new_unchecked(secs_signed, nanos_signed) }
}
}};
}
@ -244,10 +263,10 @@ impl Duration {
pub const WEEK: Self = Self::weeks(1);
/// The minimum possible duration. Adding any negative duration to this will cause an overflow.
pub const MIN: Self = Self::new_unchecked(i64::MIN, -((Nanosecond.per(Second) - 1) as i32));
pub const MIN: Self = Self::new_ranged(i64::MIN, Nanoseconds::MIN);
/// The maximum possible duration. Adding any positive duration to this will cause an overflow.
pub const MAX: Self = Self::new_unchecked(i64::MAX, (Nanosecond.per(Second) - 1) as _);
pub const MAX: Self = Self::new_ranged(i64::MAX, Nanoseconds::MAX);
// endregion constants
// region: is_{sign}
@ -259,7 +278,7 @@ impl Duration {
/// assert!(!1.nanoseconds().is_zero());
/// ```
pub const fn is_zero(self) -> bool {
self.seconds == 0 && self.nanoseconds == 0
self.seconds == 0 && self.nanoseconds.get() == 0
}
/// Check if a duration is negative.
@ -271,7 +290,7 @@ impl Duration {
/// assert!(!1.seconds().is_negative());
/// ```
pub const fn is_negative(self) -> bool {
self.seconds < 0 || self.nanoseconds < 0
self.seconds < 0 || self.nanoseconds.get() < 0
}
/// Check if a duration is positive.
@ -283,7 +302,7 @@ impl Duration {
/// assert!(!(-1).seconds().is_positive());
/// ```
pub const fn is_positive(self) -> bool {
self.seconds > 0 || self.nanoseconds > 0
self.seconds > 0 || self.nanoseconds.get() > 0
}
// endregion is_{sign}
@ -300,7 +319,7 @@ impl Duration {
/// ```
pub const fn abs(self) -> Self {
match self.seconds.checked_abs() {
Some(seconds) => Self::new_unchecked(seconds, self.nanoseconds.abs()),
Some(seconds) => Self::new_ranged_unchecked(seconds, self.nanoseconds.abs()),
None => Self::MAX,
}
}
@ -315,21 +334,36 @@ impl Duration {
/// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds());
/// ```
pub const fn unsigned_abs(self) -> StdDuration {
StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs())
StdDuration::new(
self.seconds.unsigned_abs(),
self.nanoseconds.get().unsigned_abs(),
)
}
// endregion abs
// region: constructors
/// Create a new `Duration` without checking the validity of the components.
pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
///
/// # Safety
///
/// - `nanoseconds` must be in the range `-999_999_999..=999_999_999`.
///
/// While the sign of `nanoseconds` is required to be the same as the sign of `seconds`, this is
/// not a safety invariant.
pub(crate) const unsafe fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
Self::new_ranged_unchecked(
seconds,
// Safety: The caller must uphold the safety invariants.
unsafe { Nanoseconds::new_unchecked(nanoseconds) },
)
}
/// Create a new `Duration` without checking the validity of the components.
pub(crate) const fn new_ranged_unchecked(seconds: i64, nanoseconds: Nanoseconds) -> Self {
if seconds < 0 {
debug_assert!(nanoseconds <= 0);
debug_assert!(nanoseconds > -(Nanosecond.per(Second) as i32));
debug_assert!(nanoseconds.get() <= 0);
} else if seconds > 0 {
debug_assert!(nanoseconds >= 0);
debug_assert!(nanoseconds < Nanosecond.per(Second) as _);
} else {
debug_assert!(nanoseconds.unsigned_abs() < Nanosecond.per(Second));
debug_assert!(nanoseconds.get() >= 0);
}
Self {
@ -348,24 +382,52 @@ impl Duration {
/// assert_eq!(Duration::new(-1, 0), (-1).seconds());
/// assert_eq!(Duration::new(1, 2_000_000_000), 3.seconds());
/// ```
///
/// # Panics
///
/// This may panic if an overflow occurs.
pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
seconds = expect_opt!(
seconds.checked_add(nanoseconds as i64 / Nanosecond.per(Second) as i64),
seconds.checked_add(nanoseconds as i64 / Nanosecond::per(Second) as i64),
"overflow constructing `time::Duration`"
);
nanoseconds %= Nanosecond.per(Second) as i32;
nanoseconds %= Nanosecond::per(Second) as i32;
if seconds > 0 && nanoseconds < 0 {
// `seconds` cannot overflow here because it is positive.
seconds -= 1;
nanoseconds += Nanosecond.per(Second) as i32;
nanoseconds += Nanosecond::per(Second) as i32;
} else if seconds < 0 && nanoseconds > 0 {
// `seconds` cannot overflow here because it is negative.
seconds += 1;
nanoseconds -= Nanosecond.per(Second) as i32;
nanoseconds -= Nanosecond::per(Second) as i32;
}
Self::new_unchecked(seconds, nanoseconds)
// Safety: `nanoseconds` is in range due to the modulus above.
unsafe { Self::new_unchecked(seconds, nanoseconds) }
}
/// Create a new `Duration` with the provided seconds and nanoseconds.
pub(crate) const fn new_ranged(mut seconds: i64, mut nanoseconds: Nanoseconds) -> Self {
if seconds > 0 && nanoseconds.get() < 0 {
// `seconds` cannot overflow here because it is positive.
seconds -= 1;
// Safety: `nanoseconds` is negative with a maximum of 999,999,999, so adding a billion
// to it is guaranteed to result in an in-range value.
nanoseconds = unsafe {
Nanoseconds::new_unchecked(nanoseconds.get() + Nanosecond::per(Second) as i32)
};
} else if seconds < 0 && nanoseconds.get() > 0 {
// `seconds` cannot overflow here because it is negative.
seconds += 1;
// Safety: `nanoseconds` is positive with a minimum of -999,999,999, so subtracting a
// billion from it is guaranteed to result in an in-range value.
nanoseconds = unsafe {
Nanoseconds::new_unchecked(nanoseconds.get() - Nanosecond::per(Second) as i32)
};
}
Self::new_ranged_unchecked(seconds, nanoseconds)
}
/// Create a new `Duration` with the given number of weeks. Equivalent to
@ -375,9 +437,13 @@ impl Duration {
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(Duration::weeks(1), 604_800.seconds());
/// ```
///
/// # Panics
///
/// This may panic if an overflow occurs.
pub const fn weeks(weeks: i64) -> Self {
Self::seconds(expect_opt!(
weeks.checked_mul(Second.per(Week) as _),
weeks.checked_mul(Second::per(Week) as _),
"overflow constructing `time::Duration`"
))
}
@ -389,9 +455,13 @@ impl Duration {
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(Duration::days(1), 86_400.seconds());
/// ```
///
/// # Panics
///
/// This may panic if an overflow occurs.
pub const fn days(days: i64) -> Self {
Self::seconds(expect_opt!(
days.checked_mul(Second.per(Day) as _),
days.checked_mul(Second::per(Day) as _),
"overflow constructing `time::Duration`"
))
}
@ -403,9 +473,13 @@ impl Duration {
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(Duration::hours(1), 3_600.seconds());
/// ```
///
/// # Panics
///
/// This may panic if an overflow occurs.
pub const fn hours(hours: i64) -> Self {
Self::seconds(expect_opt!(
hours.checked_mul(Second.per(Hour) as _),
hours.checked_mul(Second::per(Hour) as _),
"overflow constructing `time::Duration`"
))
}
@ -417,9 +491,13 @@ impl Duration {
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(Duration::minutes(1), 60.seconds());
/// ```
///
/// # Panics
///
/// This may panic if an overflow occurs.
pub const fn minutes(minutes: i64) -> Self {
Self::seconds(expect_opt!(
minutes.checked_mul(Second.per(Minute) as _),
minutes.checked_mul(Second::per(Minute) as _),
"overflow constructing `time::Duration`"
))
}
@ -431,7 +509,7 @@ impl Duration {
/// assert_eq!(Duration::seconds(1), 1_000.milliseconds());
/// ```
pub const fn seconds(seconds: i64) -> Self {
Self::new_unchecked(seconds, 0)
Self::new_ranged_unchecked(seconds, Nanoseconds::new_static::<0>())
}
/// Creates a new `Duration` from the specified number of seconds represented as `f64`.
@ -614,11 +692,14 @@ impl Duration {
/// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds());
/// ```
pub const fn milliseconds(milliseconds: i64) -> Self {
Self::new_unchecked(
milliseconds / Millisecond.per(Second) as i64,
(milliseconds % Millisecond.per(Second) as i64 * Nanosecond.per(Millisecond) as i64)
as _,
)
// Safety: `nanoseconds` is guaranteed to be in range because of the modulus.
unsafe {
Self::new_unchecked(
milliseconds / Millisecond::per(Second) as i64,
(milliseconds % Millisecond::per(Second) as i64
* Nanosecond::per(Millisecond) as i64) as _,
)
}
}
/// Create a new `Duration` with the given number of microseconds.
@ -629,11 +710,14 @@ impl Duration {
/// assert_eq!(Duration::microseconds(-1), (-1_000).nanoseconds());
/// ```
pub const fn microseconds(microseconds: i64) -> Self {
Self::new_unchecked(
microseconds / Microsecond.per(Second) as i64,
(microseconds % Microsecond.per(Second) as i64 * Nanosecond.per(Microsecond) as i64)
as _,
)
// Safety: `nanoseconds` is guaranteed to be in range because of the modulus.
unsafe {
Self::new_unchecked(
microseconds / Microsecond::per(Second) as i64,
(microseconds % Microsecond::per(Second) as i64
* Nanosecond::per(Microsecond) as i64) as _,
)
}
}
/// Create a new `Duration` with the given number of nanoseconds.
@ -644,10 +728,13 @@ impl Duration {
/// assert_eq!(Duration::nanoseconds(-1), (-1).microseconds() / 1_000);
/// ```
pub const fn nanoseconds(nanoseconds: i64) -> Self {
Self::new_unchecked(
nanoseconds / Nanosecond.per(Second) as i64,
(nanoseconds % Nanosecond.per(Second) as i64) as _,
)
// Safety: `nanoseconds` is guaranteed to be in range because of the modulus.
unsafe {
Self::new_unchecked(
nanoseconds / Nanosecond::per(Second) as i64,
(nanoseconds % Nanosecond::per(Second) as i64) as _,
)
}
}
/// Create a new `Duration` with the given number of nanoseconds.
@ -655,14 +742,15 @@ impl Duration {
/// As the input range cannot be fully mapped to the output, this should only be used where it's
/// known to result in a valid value.
pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self {
let seconds = nanoseconds / Nanosecond.per(Second) as i128;
let nanoseconds = nanoseconds % Nanosecond.per(Second) as i128;
let seconds = nanoseconds / Nanosecond::per(Second) as i128;
let nanoseconds = nanoseconds % Nanosecond::per(Second) as i128;
if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 {
crate::expect_failed("overflow constructing `time::Duration`");
}
Self::new_unchecked(seconds as _, nanoseconds as _)
// Safety: `nanoseconds` is guaranteed to be in range because of the modulus above.
unsafe { Self::new_unchecked(seconds as _, nanoseconds as _) }
}
// endregion constructors
@ -677,7 +765,7 @@ impl Duration {
/// assert_eq!((-6).days().whole_weeks(), 0);
/// ```
pub const fn whole_weeks(self) -> i64 {
self.whole_seconds() / Second.per(Week) as i64
self.whole_seconds() / Second::per(Week) as i64
}
/// Get the number of whole days in the duration.
@ -690,7 +778,7 @@ impl Duration {
/// assert_eq!((-23).hours().whole_days(), 0);
/// ```
pub const fn whole_days(self) -> i64 {
self.whole_seconds() / Second.per(Day) as i64
self.whole_seconds() / Second::per(Day) as i64
}
/// Get the number of whole hours in the duration.
@ -703,7 +791,7 @@ impl Duration {
/// assert_eq!((-59).minutes().whole_hours(), 0);
/// ```
pub const fn whole_hours(self) -> i64 {
self.whole_seconds() / Second.per(Hour) as i64
self.whole_seconds() / Second::per(Hour) as i64
}
/// Get the number of whole minutes in the duration.
@ -716,7 +804,7 @@ impl Duration {
/// assert_eq!((-59).seconds().whole_minutes(), 0);
/// ```
pub const fn whole_minutes(self) -> i64 {
self.whole_seconds() / Second.per(Minute) as i64
self.whole_seconds() / Second::per(Minute) as i64
}
/// Get the number of whole seconds in the duration.
@ -740,7 +828,7 @@ impl Duration {
/// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5);
/// ```
pub fn as_seconds_f64(self) -> f64 {
self.seconds as f64 + self.nanoseconds as f64 / Nanosecond.per(Second) as f64
self.seconds as f64 + self.nanoseconds.get() as f64 / Nanosecond::per(Second) as f64
}
/// Get the number of fractional seconds in the duration.
@ -751,7 +839,7 @@ impl Duration {
/// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5);
/// ```
pub fn as_seconds_f32(self) -> f32 {
self.seconds as f32 + self.nanoseconds as f32 / Nanosecond.per(Second) as f32
self.seconds as f32 + self.nanoseconds.get() as f32 / Nanosecond::per(Second) as f32
}
/// Get the number of whole milliseconds in the duration.
@ -764,13 +852,13 @@ impl Duration {
/// assert_eq!((-1).milliseconds().whole_milliseconds(), -1);
/// ```
pub const fn whole_milliseconds(self) -> i128 {
self.seconds as i128 * Millisecond.per(Second) as i128
+ self.nanoseconds as i128 / Nanosecond.per(Millisecond) as i128
self.seconds as i128 * Millisecond::per(Second) as i128
+ self.nanoseconds.get() as i128 / Nanosecond::per(Millisecond) as i128
}
/// Get the number of milliseconds past the number of whole seconds.
///
/// Always in the range `-1_000..1_000`.
/// Always in the range `-999..=999`.
///
/// ```rust
/// # use time::ext::NumericalDuration;
@ -779,7 +867,7 @@ impl Duration {
/// ```
// Allow the lint, as the value is guaranteed to be less than 1000.
pub const fn subsec_milliseconds(self) -> i16 {
(self.nanoseconds / Nanosecond.per(Millisecond) as i32) as _
(self.nanoseconds.get() / Nanosecond::per(Millisecond) as i32) as _
}
/// Get the number of whole microseconds in the duration.
@ -792,13 +880,13 @@ impl Duration {
/// assert_eq!((-1).microseconds().whole_microseconds(), -1);
/// ```
pub const fn whole_microseconds(self) -> i128 {
self.seconds as i128 * Microsecond.per(Second) as i128
+ self.nanoseconds as i128 / Nanosecond.per(Microsecond) as i128
self.seconds as i128 * Microsecond::per(Second) as i128
+ self.nanoseconds.get() as i128 / Nanosecond::per(Microsecond) as i128
}
/// Get the number of microseconds past the number of whole seconds.
///
/// Always in the range `-1_000_000..1_000_000`.
/// Always in the range `-999_999..=999_999`.
///
/// ```rust
/// # use time::ext::NumericalDuration;
@ -806,7 +894,7 @@ impl Duration {
/// assert_eq!((-1.0004).seconds().subsec_microseconds(), -400);
/// ```
pub const fn subsec_microseconds(self) -> i32 {
self.nanoseconds / Nanosecond.per(Microsecond) as i32
self.nanoseconds.get() / Nanosecond::per(Microsecond) as i32
}
/// Get the number of nanoseconds in the duration.
@ -819,12 +907,12 @@ impl Duration {
/// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1);
/// ```
pub const fn whole_nanoseconds(self) -> i128 {
self.seconds as i128 * Nanosecond.per(Second) as i128 + self.nanoseconds as i128
self.seconds as i128 * Nanosecond::per(Second) as i128 + self.nanoseconds.get() as i128
}
/// Get the number of nanoseconds past the number of whole seconds.
///
/// The returned value will always be in the range `-1_000_000_000..1_000_000_000`.
/// The returned value will always be in the range `-999_999_999..=999_999_999`.
///
/// ```rust
/// # use time::ext::NumericalDuration;
@ -832,6 +920,12 @@ impl Duration {
/// assert_eq!((-1.000_000_400).seconds().subsec_nanoseconds(), -400);
/// ```
pub const fn subsec_nanoseconds(self) -> i32 {
self.nanoseconds.get()
}
/// Get the number of nanoseconds past the number of whole seconds.
#[cfg(feature = "quickcheck")]
pub(crate) const fn subsec_nanoseconds_ranged(self) -> Nanoseconds {
self.nanoseconds
}
// endregion getters
@ -847,18 +941,19 @@ impl Duration {
/// ```
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds));
let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
let mut nanoseconds = self.nanoseconds.get() + rhs.nanoseconds.get();
if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond.per(Second) as i32;
if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond::per(Second) as i32;
seconds = const_try_opt!(seconds.checked_add(1));
} else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
} else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0
{
nanoseconds += Nanosecond.per(Second) as i32;
nanoseconds += Nanosecond::per(Second) as i32;
seconds = const_try_opt!(seconds.checked_sub(1));
}
Some(Self::new_unchecked(seconds, nanoseconds))
// Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling.
unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) }
}
/// Computes `self - rhs`, returning `None` if an overflow occurred.
@ -871,18 +966,19 @@ impl Duration {
/// ```
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds));
let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
let mut nanoseconds = self.nanoseconds.get() - rhs.nanoseconds.get();
if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond.per(Second) as i32;
if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond::per(Second) as i32;
seconds = const_try_opt!(seconds.checked_add(1));
} else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
} else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0
{
nanoseconds += Nanosecond.per(Second) as i32;
nanoseconds += Nanosecond::per(Second) as i32;
seconds = const_try_opt!(seconds.checked_sub(1));
}
Some(Self::new_unchecked(seconds, nanoseconds))
// Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling.
unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) }
}
/// Computes `self * rhs`, returning `None` if an overflow occurred.
@ -897,14 +993,15 @@ impl Duration {
/// ```
pub const fn checked_mul(self, rhs: i32) -> Option<Self> {
// Multiply nanoseconds as i64, because it cannot overflow that way.
let total_nanos = self.nanoseconds as i64 * rhs as i64;
let extra_secs = total_nanos / Nanosecond.per(Second) as i64;
let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _;
let total_nanos = self.nanoseconds.get() as i64 * rhs as i64;
let extra_secs = total_nanos / Nanosecond::per(Second) as i64;
let nanoseconds = (total_nanos % Nanosecond::per(Second) as i64) as _;
let seconds = const_try_opt!(
const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs)
);
Some(Self::new_unchecked(seconds, nanoseconds))
// Safety: `nanoseconds` is guaranteed to be in range because of the modulus above.
unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) }
}
/// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow.
@ -916,13 +1013,35 @@ impl Duration {
/// assert_eq!(1.seconds().checked_div(0), None);
/// ```
pub const fn checked_div(self, rhs: i32) -> Option<Self> {
let seconds = const_try_opt!(self.seconds.checked_div(rhs as i64));
let carry = self.seconds - seconds * (rhs as i64);
let extra_nanos =
const_try_opt!((carry * Nanosecond.per(Second) as i64).checked_div(rhs as i64));
let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32);
let (secs, extra_secs) = (
const_try_opt!(self.seconds.checked_div(rhs as i64)),
self.seconds % (rhs as i64),
);
let (mut nanos, extra_nanos) = (self.nanoseconds.get() / rhs, self.nanoseconds.get() % rhs);
nanos += ((extra_secs * (Nanosecond::per(Second) as i64) + extra_nanos as i64)
/ (rhs as i64)) as i32;
Some(Self::new_unchecked(seconds, nanoseconds))
// Safety: `nanoseconds` is in range.
unsafe { Some(Self::new_unchecked(secs, nanos)) }
}
/// Computes `-self`, returning `None` if the result would overflow.
///
/// ```rust
/// # use time::ext::NumericalDuration;
/// # use time::Duration;
/// assert_eq!(5.seconds().checked_neg(), Some((-5).seconds()));
/// assert_eq!(Duration::MIN.checked_neg(), None);
/// ```
pub const fn checked_neg(self) -> Option<Self> {
if self.seconds == i64::MIN {
None
} else {
Some(Self::new_ranged_unchecked(
-self.seconds,
self.nanoseconds.neg(),
))
}
}
// endregion checked arithmetic
@ -947,24 +1066,25 @@ impl Duration {
}
return Self::MIN;
}
let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
let mut nanoseconds = self.nanoseconds.get() + rhs.nanoseconds.get();
if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond.per(Second) as i32;
if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond::per(Second) as i32;
seconds = match seconds.checked_add(1) {
Some(seconds) => seconds,
None => return Self::MAX,
};
} else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
} else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0
{
nanoseconds += Nanosecond.per(Second) as i32;
nanoseconds += Nanosecond::per(Second) as i32;
seconds = match seconds.checked_sub(1) {
Some(seconds) => seconds,
None => return Self::MIN,
};
}
Self::new_unchecked(seconds, nanoseconds)
// Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling.
unsafe { Self::new_unchecked(seconds, nanoseconds) }
}
/// Computes `self - rhs`, saturating if an overflow occurred.
@ -987,24 +1107,25 @@ impl Duration {
}
return Self::MIN;
}
let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
let mut nanoseconds = self.nanoseconds.get() - rhs.nanoseconds.get();
if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond.per(Second) as i32;
if nanoseconds >= Nanosecond::per(Second) as _ || seconds < 0 && nanoseconds > 0 {
nanoseconds -= Nanosecond::per(Second) as i32;
seconds = match seconds.checked_add(1) {
Some(seconds) => seconds,
None => return Self::MAX,
};
} else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
} else if nanoseconds <= -(Nanosecond::per(Second) as i32) || seconds > 0 && nanoseconds < 0
{
nanoseconds += Nanosecond.per(Second) as i32;
nanoseconds += Nanosecond::per(Second) as i32;
seconds = match seconds.checked_sub(1) {
Some(seconds) => seconds,
None => return Self::MIN,
};
}
Self::new_unchecked(seconds, nanoseconds)
// Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling.
unsafe { Self::new_unchecked(seconds, nanoseconds) }
}
/// Computes `self * rhs`, saturating if an overflow occurred.
@ -1021,9 +1142,9 @@ impl Duration {
/// ```
pub const fn saturating_mul(self, rhs: i32) -> Self {
// Multiply nanoseconds as i64, because it cannot overflow that way.
let total_nanos = self.nanoseconds as i64 * rhs as i64;
let extra_secs = total_nanos / Nanosecond.per(Second) as i64;
let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _;
let total_nanos = self.nanoseconds.get() as i64 * rhs as i64;
let extra_secs = total_nanos / Nanosecond::per(Second) as i64;
let nanoseconds = (total_nanos % Nanosecond::per(Second) as i64) as _;
let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _);
if overflow1 {
if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 {
@ -1039,13 +1160,19 @@ impl Duration {
return Self::MIN;
}
Self::new_unchecked(seconds, nanoseconds)
// Safety: `nanoseconds` is guaranteed to be in range because of to the modulus above.
unsafe { Self::new_unchecked(seconds, nanoseconds) }
}
// endregion saturating arithmetic
/// Runs a closure, returning the duration of time it took to run. The return value of the
/// closure is provided in the second part of the tuple.
#[cfg(feature = "std")]
#[deprecated(
since = "0.3.32",
note = "extremely limited use case, not intended for benchmarking"
)]
#[allow(deprecated)]
pub fn time_fn<T>(f: impl FnOnce() -> T) -> (Self, T) {
let start = Instant::now();
let return_value = f();
@ -1097,13 +1224,13 @@ impl fmt::Display for Duration {
// Even if this produces a de-normal float, because we're rounding we don't really care.
let seconds = self.unsigned_abs().as_secs_f64();
item!("d", seconds / Second.per(Day) as f64);
item!("h", seconds / Second.per(Hour) as f64);
item!("m", seconds / Second.per(Minute) as f64);
item!("d", seconds / Second::per(Day) as f64);
item!("h", seconds / Second::per(Hour) as f64);
item!("m", seconds / Second::per(Minute) as f64);
item!("s", seconds);
item!("ms", seconds * Millisecond.per(Second) as f64);
item!("µs", seconds * Microsecond.per(Second) as f64);
item!("ns", seconds * Nanosecond.per(Second) as f64);
item!("ms", seconds * Millisecond::per(Second) as f64);
item!("µs", seconds * Microsecond::per(Second) as f64);
item!("ns", seconds * Nanosecond::per(Second) as f64);
} else {
// Precise, but verbose representation.
@ -1122,25 +1249,28 @@ impl fmt::Display for Duration {
}
let seconds = self.seconds.unsigned_abs();
let nanoseconds = self.nanoseconds.unsigned_abs();
let nanoseconds = self.nanoseconds.get().unsigned_abs();
item!("d", seconds / Second.per(Day) as u64)?;
item!("d", seconds / Second::per(Day).extend::<u64>())?;
item!(
"h",
seconds / Second.per(Hour) as u64 % Hour.per(Day) as u64
seconds / Second::per(Hour).extend::<u64>() % Hour::per(Day).extend::<u64>()
)?;
item!(
"m",
seconds / Second.per(Minute) as u64 % Minute.per(Hour) as u64
seconds / Second::per(Minute).extend::<u64>() % Minute::per(Hour).extend::<u64>()
)?;
item!("s", seconds % Second.per(Minute) as u64)?;
item!("ms", nanoseconds / Nanosecond.per(Millisecond))?;
item!("s", seconds % Second::per(Minute).extend::<u64>())?;
item!("ms", nanoseconds / Nanosecond::per(Millisecond))?;
item!(
"µs",
nanoseconds / Nanosecond.per(Microsecond) as u32
% Microsecond.per(Millisecond) as u32
nanoseconds / Nanosecond::per(Microsecond).extend::<u32>()
% Microsecond::per(Millisecond).extend::<u32>()
)?;
item!(
"ns",
nanoseconds % Nanosecond::per(Microsecond).extend::<u32>()
)?;
item!("ns", nanoseconds % Nanosecond.per(Microsecond) as u32)?;
}
Ok(())
@ -1156,7 +1286,7 @@ impl TryFrom<StdDuration> for Duration {
.as_secs()
.try_into()
.map_err(|_| error::ConversionRange)?,
original.subsec_nanos() as _,
original.subsec_nanos().cast_signed(),
))
}
}
@ -1172,6 +1302,7 @@ impl TryFrom<Duration> for StdDuration {
.map_err(|_| error::ConversionRange)?,
duration
.nanoseconds
.get()
.try_into()
.map_err(|_| error::ConversionRange)?,
))
@ -1181,6 +1312,9 @@ impl TryFrom<Duration> for StdDuration {
impl Add for Duration {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, rhs: Self) -> Self::Output {
self.checked_add(rhs)
.expect("overflow when adding durations")
@ -1190,6 +1324,9 @@ impl Add for Duration {
impl Add<StdDuration> for Duration {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, std_duration: StdDuration) -> Self::Output {
self + Self::try_from(std_duration)
.expect("overflow converting `std::time::Duration` to `time::Duration`")
@ -1207,6 +1344,9 @@ impl Add<Duration> for StdDuration {
impl_add_assign!(Duration: Self, StdDuration);
impl AddAssign<Duration> for StdDuration {
/// # Panics
///
/// This may panic if the resulting addition cannot be represented.
fn add_assign(&mut self, rhs: Duration) {
*self = (*self + rhs).try_into().expect(
"Cannot represent a resulting duration in std. Try `let x = x + rhs;`, which will \
@ -1219,13 +1359,16 @@ impl Neg for Duration {
type Output = Self;
fn neg(self) -> Self::Output {
Self::new_unchecked(-self.seconds, -self.nanoseconds)
self.checked_neg().expect("overflow when negating duration")
}
}
impl Sub for Duration {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, rhs: Self) -> Self::Output {
self.checked_sub(rhs)
.expect("overflow when subtracting durations")
@ -1235,6 +1378,9 @@ impl Sub for Duration {
impl Sub<StdDuration> for Duration {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, rhs: StdDuration) -> Self::Output {
self - Self::try_from(rhs)
.expect("overflow converting `std::time::Duration` to `time::Duration`")
@ -1244,6 +1390,9 @@ impl Sub<StdDuration> for Duration {
impl Sub<Duration> for StdDuration {
type Output = Duration;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, rhs: Duration) -> Self::Output {
Duration::try_from(self)
.expect("overflow converting `std::time::Duration` to `time::Duration`")
@ -1254,6 +1403,9 @@ impl Sub<Duration> for StdDuration {
impl_sub_assign!(Duration: Self, StdDuration);
impl SubAssign<Duration> for StdDuration {
/// # Panics
///
/// This may panic if the resulting subtraction can not be represented.
fn sub_assign(&mut self, rhs: Duration) {
*self = (*self - rhs).try_into().expect(
"Cannot represent a resulting duration in std. Try `let x = x - rhs;`, which will \
@ -1271,7 +1423,7 @@ macro_rules! duration_mul_div_int {
fn mul(self, rhs: $type) -> Self::Output {
Self::nanoseconds_i128(
self.whole_nanoseconds()
.checked_mul(rhs as _)
.checked_mul(rhs.cast_signed().extend::<i128>())
.expect("overflow when multiplying duration")
)
}
@ -1289,7 +1441,9 @@ macro_rules! duration_mul_div_int {
type Output = Self;
fn div(self, rhs: $type) -> Self::Output {
Self::nanoseconds_i128(self.whole_nanoseconds() / rhs as i128)
Self::nanoseconds_i128(
self.whole_nanoseconds() / rhs.cast_signed().extend::<i128>()
)
}
}
)+};
@ -1386,14 +1540,18 @@ impl PartialEq<Duration> for StdDuration {
impl PartialOrd<StdDuration> for Duration {
fn partial_cmp(&self, rhs: &StdDuration) -> Option<Ordering> {
if rhs.as_secs() > i64::MAX as _ {
if rhs.as_secs() > i64::MAX.cast_unsigned() {
return Some(Ordering::Less);
}
Some(
self.seconds
.cmp(&(rhs.as_secs() as _))
.then_with(|| self.nanoseconds.cmp(&(rhs.subsec_nanos() as _))),
.cmp(&rhs.as_secs().cast_signed())
.then_with(|| {
self.nanoseconds
.get()
.cmp(&rhs.subsec_nanos().cast_signed())
}),
)
}
}

View file

@ -36,31 +36,47 @@ pub use parse_from_description::ParseFromDescription;
#[cfg(feature = "parsing")]
pub use try_from_parsed::TryFromParsed;
#[cfg(feature = "parsing")]
use crate::internal_macros::bug;
/// A unified error type for anything returned by a method in the time crate.
///
/// This can be used when you either don't know or don't care about the exact error returned.
/// `Result<_, time::Error>` (or its alias `time::Result<_>`) will work in these situations.
#[allow(missing_copy_implementations, variant_size_differences)]
#[allow(clippy::missing_docs_in_private_items)] // variants only
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
#[allow(missing_docs)]
ConversionRange(ConversionRange),
#[allow(missing_docs)]
ComponentRange(ComponentRange),
#[cfg(feature = "local-offset")]
#[allow(missing_docs)]
IndeterminateOffset(IndeterminateOffset),
#[cfg(feature = "formatting")]
#[allow(missing_docs)]
Format(Format),
#[cfg(feature = "parsing")]
#[allow(missing_docs)]
ParseFromDescription(ParseFromDescription),
#[cfg(feature = "parsing")]
#[allow(missing_docs)]
#[non_exhaustive]
#[deprecated(
since = "0.3.28",
note = "no longer output. moved to the `ParseFromDescription` variant"
)]
UnexpectedTrailingCharacters,
#[cfg(feature = "parsing")]
#[allow(missing_docs)]
TryFromParsed(TryFromParsed),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
#[allow(missing_docs)]
InvalidFormatDescription(InvalidFormatDescription),
#[allow(missing_docs)]
DifferentVariant(DifferentVariant),
#[allow(missing_docs)]
InvalidVariant(InvalidVariant),
}
@ -76,7 +92,8 @@ impl fmt::Display for Error {
#[cfg(feature = "parsing")]
Self::ParseFromDescription(e) => e.fmt(f),
#[cfg(feature = "parsing")]
Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters => bug!("variant should not be used"),
#[cfg(feature = "parsing")]
Self::TryFromParsed(e) => e.fmt(f),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
@ -100,7 +117,8 @@ impl std::error::Error for Error {
#[cfg(feature = "parsing")]
Self::ParseFromDescription(err) => Some(err),
#[cfg(feature = "parsing")]
Self::UnexpectedTrailingCharacters => None,
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters => bug!("variant should not be used"),
#[cfg(feature = "parsing")]
Self::TryFromParsed(err) => Some(err),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]

View file

@ -3,18 +3,23 @@
use core::fmt;
use crate::error::{self, ParseFromDescription, TryFromParsed};
use crate::internal_macros::bug;
/// An error that occurred at some stage of parsing.
#[allow(variant_size_differences)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Parse {
#[allow(clippy::missing_docs_in_private_items)]
#[allow(missing_docs)]
TryFromParsed(TryFromParsed),
#[allow(clippy::missing_docs_in_private_items)]
#[allow(missing_docs)]
ParseFromDescription(ParseFromDescription),
/// The input should have ended, but there were characters remaining.
#[non_exhaustive]
#[deprecated(
since = "0.3.28",
note = "no longer output. moved to the `ParseFromDescription` variant"
)]
UnexpectedTrailingCharacters,
}
@ -23,7 +28,8 @@ impl fmt::Display for Parse {
match self {
Self::TryFromParsed(err) => err.fmt(f),
Self::ParseFromDescription(err) => err.fmt(f),
Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters => bug!("variant should not be used"),
}
}
}
@ -34,7 +40,8 @@ impl std::error::Error for Parse {
match self {
Self::TryFromParsed(err) => Some(err),
Self::ParseFromDescription(err) => Some(err),
Self::UnexpectedTrailingCharacters => None,
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters => bug!("variant should not be used"),
}
}
}
@ -78,7 +85,8 @@ impl From<Parse> for crate::Error {
match err {
Parse::TryFromParsed(err) => Self::TryFromParsed(err),
Parse::ParseFromDescription(err) => Self::ParseFromDescription(err),
Parse::UnexpectedTrailingCharacters => Self::UnexpectedTrailingCharacters,
#[allow(deprecated)]
Parse::UnexpectedTrailingCharacters => bug!("variant should not be used"),
}
}
}
@ -89,7 +97,8 @@ impl TryFrom<crate::Error> for Parse {
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)),
crate::Error::UnexpectedTrailingCharacters => Ok(Self::UnexpectedTrailingCharacters),
#[allow(deprecated)]
crate::Error::UnexpectedTrailingCharacters => bug!("variant should not be used"),
crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)),
_ => Err(error::DifferentVariant),
}

View file

@ -13,6 +13,9 @@ pub enum ParseFromDescription {
InvalidLiteral,
/// A dynamic component was not valid.
InvalidComponent(&'static str),
/// The input was expected to have ended, but there are characters that remain.
#[non_exhaustive]
UnexpectedTrailingCharacters,
}
impl fmt::Display for ParseFromDescription {
@ -22,6 +25,9 @@ impl fmt::Display for ParseFromDescription {
Self::InvalidComponent(name) => {
write!(f, "the '{name}' component could not be parsed")
}
Self::UnexpectedTrailingCharacters => {
f.write_str("unexpected trailing characters; the end of input was expected")
}
}
}
}

View file

@ -1,280 +0,0 @@
//! Extension traits.
use core::time::Duration as StdDuration;
use crate::convert::*;
use crate::Duration;
/// Sealed trait to prevent downstream implementations.
mod sealed {
/// A trait that cannot be implemented by downstream users.
pub trait Sealed {}
impl Sealed for i64 {}
impl Sealed for u64 {}
impl Sealed for f64 {}
}
// region: NumericalDuration
/// Create [`Duration`]s from numeric literals.
///
/// # Examples
///
/// Basic construction of [`Duration`]s.
///
/// ```rust
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5));
/// assert_eq!(5.microseconds(), Duration::microseconds(5));
/// assert_eq!(5.milliseconds(), Duration::milliseconds(5));
/// assert_eq!(5.seconds(), Duration::seconds(5));
/// assert_eq!(5.minutes(), Duration::minutes(5));
/// assert_eq!(5.hours(), Duration::hours(5));
/// assert_eq!(5.days(), Duration::days(5));
/// assert_eq!(5.weeks(), Duration::weeks(5));
/// ```
///
/// Signed integers work as well!
///
/// ```rust
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5));
/// assert_eq!((-5).microseconds(), Duration::microseconds(-5));
/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5));
/// assert_eq!((-5).seconds(), Duration::seconds(-5));
/// assert_eq!((-5).minutes(), Duration::minutes(-5));
/// assert_eq!((-5).hours(), Duration::hours(-5));
/// assert_eq!((-5).days(), Duration::days(-5));
/// assert_eq!((-5).weeks(), Duration::weeks(-5));
/// ```
///
/// Just like any other [`Duration`], they can be added, subtracted, etc.
///
/// ```rust
/// # use time::ext::NumericalDuration;
/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds());
/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds());
/// ```
///
/// When called on floating point values, any remainder of the floating point value will be
/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
/// capacity.
pub trait NumericalDuration: sealed::Sealed {
/// Create a [`Duration`] from the number of nanoseconds.
fn nanoseconds(self) -> Duration;
/// Create a [`Duration`] from the number of microseconds.
fn microseconds(self) -> Duration;
/// Create a [`Duration`] from the number of milliseconds.
fn milliseconds(self) -> Duration;
/// Create a [`Duration`] from the number of seconds.
fn seconds(self) -> Duration;
/// Create a [`Duration`] from the number of minutes.
fn minutes(self) -> Duration;
/// Create a [`Duration`] from the number of hours.
fn hours(self) -> Duration;
/// Create a [`Duration`] from the number of days.
fn days(self) -> Duration;
/// Create a [`Duration`] from the number of weeks.
fn weeks(self) -> Duration;
}
impl NumericalDuration for i64 {
fn nanoseconds(self) -> Duration {
Duration::nanoseconds(self)
}
fn microseconds(self) -> Duration {
Duration::microseconds(self)
}
fn milliseconds(self) -> Duration {
Duration::milliseconds(self)
}
fn seconds(self) -> Duration {
Duration::seconds(self)
}
fn minutes(self) -> Duration {
Duration::minutes(self)
}
fn hours(self) -> Duration {
Duration::hours(self)
}
fn days(self) -> Duration {
Duration::days(self)
}
fn weeks(self) -> Duration {
Duration::weeks(self)
}
}
impl NumericalDuration for f64 {
fn nanoseconds(self) -> Duration {
Duration::nanoseconds(self as _)
}
fn microseconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Microsecond) as Self) as _)
}
fn milliseconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Millisecond) as Self) as _)
}
fn seconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Second) as Self) as _)
}
fn minutes(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Minute) as Self) as _)
}
fn hours(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Hour) as Self) as _)
}
fn days(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Day) as Self) as _)
}
fn weeks(self) -> Duration {
Duration::nanoseconds((self * Nanosecond.per(Week) as Self) as _)
}
}
// endregion NumericalDuration
// region: NumericalStdDuration
/// Create [`std::time::Duration`]s from numeric literals.
///
/// # Examples
///
/// Basic construction of [`std::time::Duration`]s.
///
/// ```rust
/// # use time::ext::NumericalStdDuration;
/// # use core::time::Duration;
/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5));
/// assert_eq!(5.std_microseconds(), Duration::from_micros(5));
/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5));
/// assert_eq!(5.std_seconds(), Duration::from_secs(5));
/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60));
/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600));
/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400));
/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800));
/// ```
///
/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc.
///
/// ```rust
/// # use time::ext::NumericalStdDuration;
/// assert_eq!(
/// 2.std_seconds() + 500.std_milliseconds(),
/// 2_500.std_milliseconds()
/// );
/// assert_eq!(
/// 2.std_seconds() - 500.std_milliseconds(),
/// 1_500.std_milliseconds()
/// );
/// ```
///
/// When called on floating point values, any remainder of the floating point value will be
/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
/// capacity.
pub trait NumericalStdDuration: sealed::Sealed {
/// Create a [`std::time::Duration`] from the number of nanoseconds.
fn std_nanoseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of microseconds.
fn std_microseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of milliseconds.
fn std_milliseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of seconds.
fn std_seconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of minutes.
fn std_minutes(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of hours.
fn std_hours(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of days.
fn std_days(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of weeks.
fn std_weeks(self) -> StdDuration;
}
impl NumericalStdDuration for u64 {
fn std_nanoseconds(self) -> StdDuration {
StdDuration::from_nanos(self)
}
fn std_microseconds(self) -> StdDuration {
StdDuration::from_micros(self)
}
fn std_milliseconds(self) -> StdDuration {
StdDuration::from_millis(self)
}
fn std_seconds(self) -> StdDuration {
StdDuration::from_secs(self)
}
fn std_minutes(self) -> StdDuration {
StdDuration::from_secs(self * Second.per(Minute) as Self)
}
fn std_hours(self) -> StdDuration {
StdDuration::from_secs(self * Second.per(Hour) as Self)
}
fn std_days(self) -> StdDuration {
StdDuration::from_secs(self * Second.per(Day) as Self)
}
fn std_weeks(self) -> StdDuration {
StdDuration::from_secs(self * Second.per(Week) as Self)
}
}
impl NumericalStdDuration for f64 {
fn std_nanoseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos(self as _)
}
fn std_microseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Microsecond) as Self) as _)
}
fn std_milliseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Millisecond) as Self) as _)
}
fn std_seconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Second) as Self) as _)
}
fn std_minutes(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Minute) as Self) as _)
}
fn std_hours(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Hour) as Self) as _)
}
fn std_days(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Day) as Self) as _)
}
fn std_weeks(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond.per(Week) as Self) as _)
}
}
// endregion NumericalStdDuration

View file

@ -0,0 +1,26 @@
use num_conv::prelude::*;
/// A trait that indicates the formatted width of the value can be determined.
///
/// Note that this should not be implemented for any signed integers. This forces the caller to
/// write the sign if desired.
pub(crate) trait DigitCount {
/// The number of digits in the stringified value.
fn num_digits(self) -> u8;
}
/// A macro to generate implementations of `DigitCount` for unsigned integers.
macro_rules! impl_digit_count {
($($t:ty),* $(,)?) => {
$(impl DigitCount for $t {
fn num_digits(self) -> u8 {
match self.checked_ilog10() {
Some(n) => n.truncate::<u8>() + 1,
None => 1,
}
}
})*
};
}
impl_digit_count!(u8, u16, u32);

100
third_party/rust/time/src/ext/instant.rs vendored Normal file
View file

@ -0,0 +1,100 @@
use std::time::Instant as StdInstant;
use crate::Duration;
/// Sealed trait to prevent downstream implementations.
mod sealed {
/// A trait that cannot be implemented by downstream users.
pub trait Sealed: Sized {}
impl Sealed for std::time::Instant {}
}
/// An extension trait for [`std::time::Instant`] that adds methods for
/// [`time::Duration`](Duration)s.
pub trait InstantExt: sealed::Sealed {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure. See [`InstantExt::checked_add_signed`] for a non-panicking
/// version.
fn add_signed(self, duration: Duration) -> Self {
self.checked_add_signed(duration)
.expect("overflow when adding duration to instant")
}
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure. See [`InstantExt::checked_sub_signed`] for a non-panicking
/// version.
fn sub_signed(self, duration: Duration) -> Self {
self.checked_sub_signed(duration)
.expect("overflow when subtracting duration from instant")
}
/// Returns `Some(t)` where `t` is the time `self.checked_add_signed(duration)` if `t` can be
/// represented as `Instant` (which means it's inside the bounds of the underlying data
/// structure), `None` otherwise.
fn checked_add_signed(&self, duration: Duration) -> Option<Self>;
/// Returns `Some(t)` where `t` is the time `self.checked_sub_signed(duration)` if `t` can be
/// represented as `Instant` (which means it's inside the bounds of the underlying data
/// structure), `None` otherwise.
fn checked_sub_signed(&self, duration: Duration) -> Option<Self>;
/// Returns the amount of time elapsed from another instant to this one. This will be negative
/// if `earlier` is later than `self`.
///
/// # Example
///
/// ```rust
/// # use std::thread::sleep;
/// # use std::time::{Duration, Instant};
/// # use time::ext::InstantExt;
/// let now = Instant::now();
/// sleep(Duration::new(1, 0));
/// let new_now = Instant::now();
/// println!("{:?}", new_now.signed_duration_since(now)); // positive
/// println!("{:?}", now.signed_duration_since(new_now)); // negative
/// ```
fn signed_duration_since(&self, earlier: Self) -> Duration;
}
impl InstantExt for StdInstant {
fn checked_add_signed(&self, duration: Duration) -> Option<Self> {
if duration.is_positive() {
self.checked_add(duration.unsigned_abs())
} else if duration.is_negative() {
#[allow(clippy::unchecked_duration_subtraction)]
self.checked_sub(duration.unsigned_abs())
} else {
debug_assert!(duration.is_zero());
Some(*self)
}
}
fn checked_sub_signed(&self, duration: Duration) -> Option<Self> {
if duration.is_positive() {
#[allow(clippy::unchecked_duration_subtraction)]
self.checked_sub(duration.unsigned_abs())
} else if duration.is_negative() {
self.checked_add(duration.unsigned_abs())
} else {
debug_assert!(duration.is_zero());
Some(*self)
}
}
fn signed_duration_since(&self, earlier: Self) -> Duration {
if *self > earlier {
self.saturating_duration_since(earlier)
.try_into()
.unwrap_or(Duration::MAX)
} else {
earlier
.saturating_duration_since(*self)
.try_into()
.map_or(Duration::MIN, |d: Duration| -d)
}
}
}

13
third_party/rust/time/src/ext/mod.rs vendored Normal file
View file

@ -0,0 +1,13 @@
//! Extension traits.
mod digit_count;
#[cfg(feature = "std")]
mod instant;
mod numerical_duration;
mod numerical_std_duration;
pub(crate) use self::digit_count::DigitCount;
#[cfg(feature = "std")]
pub use self::instant::InstantExt;
pub use self::numerical_duration::NumericalDuration;
pub use self::numerical_std_duration::NumericalStdDuration;

View file

@ -0,0 +1,140 @@
use crate::convert::*;
use crate::Duration;
/// Sealed trait to prevent downstream implementations.
mod sealed {
/// A trait that cannot be implemented by downstream users.
pub trait Sealed {}
impl Sealed for i64 {}
impl Sealed for f64 {}
}
/// Create [`Duration`]s from numeric literals.
///
/// # Examples
///
/// Basic construction of [`Duration`]s.
///
/// ```rust
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5));
/// assert_eq!(5.microseconds(), Duration::microseconds(5));
/// assert_eq!(5.milliseconds(), Duration::milliseconds(5));
/// assert_eq!(5.seconds(), Duration::seconds(5));
/// assert_eq!(5.minutes(), Duration::minutes(5));
/// assert_eq!(5.hours(), Duration::hours(5));
/// assert_eq!(5.days(), Duration::days(5));
/// assert_eq!(5.weeks(), Duration::weeks(5));
/// ```
///
/// Signed integers work as well!
///
/// ```rust
/// # use time::{Duration, ext::NumericalDuration};
/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5));
/// assert_eq!((-5).microseconds(), Duration::microseconds(-5));
/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5));
/// assert_eq!((-5).seconds(), Duration::seconds(-5));
/// assert_eq!((-5).minutes(), Duration::minutes(-5));
/// assert_eq!((-5).hours(), Duration::hours(-5));
/// assert_eq!((-5).days(), Duration::days(-5));
/// assert_eq!((-5).weeks(), Duration::weeks(-5));
/// ```
///
/// Just like any other [`Duration`], they can be added, subtracted, etc.
///
/// ```rust
/// # use time::ext::NumericalDuration;
/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds());
/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds());
/// ```
///
/// When called on floating point values, any remainder of the floating point value will be
/// truncated. Keep in mind that floating point numbers are inherently imprecise and have
/// limited capacity.
pub trait NumericalDuration: sealed::Sealed {
/// Create a [`Duration`] from the number of nanoseconds.
fn nanoseconds(self) -> Duration;
/// Create a [`Duration`] from the number of microseconds.
fn microseconds(self) -> Duration;
/// Create a [`Duration`] from the number of milliseconds.
fn milliseconds(self) -> Duration;
/// Create a [`Duration`] from the number of seconds.
fn seconds(self) -> Duration;
/// Create a [`Duration`] from the number of minutes.
fn minutes(self) -> Duration;
/// Create a [`Duration`] from the number of hours.
fn hours(self) -> Duration;
/// Create a [`Duration`] from the number of days.
fn days(self) -> Duration;
/// Create a [`Duration`] from the number of weeks.
fn weeks(self) -> Duration;
}
impl NumericalDuration for i64 {
fn nanoseconds(self) -> Duration {
Duration::nanoseconds(self)
}
fn microseconds(self) -> Duration {
Duration::microseconds(self)
}
fn milliseconds(self) -> Duration {
Duration::milliseconds(self)
}
fn seconds(self) -> Duration {
Duration::seconds(self)
}
fn minutes(self) -> Duration {
Duration::minutes(self)
}
fn hours(self) -> Duration {
Duration::hours(self)
}
fn days(self) -> Duration {
Duration::days(self)
}
fn weeks(self) -> Duration {
Duration::weeks(self)
}
}
impl NumericalDuration for f64 {
fn nanoseconds(self) -> Duration {
Duration::nanoseconds(self as _)
}
fn microseconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Microsecond) as Self) as _)
}
fn milliseconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Millisecond) as Self) as _)
}
fn seconds(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Second) as Self) as _)
}
fn minutes(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Minute) as Self) as _)
}
fn hours(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Hour) as Self) as _)
}
fn days(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Day) as Self) as _)
}
fn weeks(self) -> Duration {
Duration::nanoseconds((self * Nanosecond::per(Week) as Self) as _)
}
}

View file

@ -0,0 +1,192 @@
use core::time::Duration as StdDuration;
use num_conv::prelude::*;
use crate::convert::*;
/// Sealed trait to prevent downstream implementations.
mod sealed {
/// A trait that cannot be implemented by downstream users.
pub trait Sealed {}
impl Sealed for u64 {}
impl Sealed for f64 {}
}
/// Create [`std::time::Duration`]s from numeric literals.
///
/// # Examples
///
/// Basic construction of [`std::time::Duration`]s.
///
/// ```rust
/// # use time::ext::NumericalStdDuration;
/// # use core::time::Duration;
/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5));
/// assert_eq!(5.std_microseconds(), Duration::from_micros(5));
/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5));
/// assert_eq!(5.std_seconds(), Duration::from_secs(5));
/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60));
/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600));
/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400));
/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800));
/// ```
///
/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc.
///
/// ```rust
/// # use time::ext::NumericalStdDuration;
/// assert_eq!(
/// 2.std_seconds() + 500.std_milliseconds(),
/// 2_500.std_milliseconds()
/// );
/// assert_eq!(
/// 2.std_seconds() - 500.std_milliseconds(),
/// 1_500.std_milliseconds()
/// );
/// ```
///
/// When called on floating point values, any remainder of the floating point value will be
/// truncated. Keep in mind that floating point numbers are inherently imprecise and have
/// limited capacity.
pub trait NumericalStdDuration: sealed::Sealed {
/// Create a [`std::time::Duration`] from the number of nanoseconds.
fn std_nanoseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of microseconds.
fn std_microseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of milliseconds.
fn std_milliseconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of seconds.
fn std_seconds(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of minutes.
fn std_minutes(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of hours.
fn std_hours(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of days.
fn std_days(self) -> StdDuration;
/// Create a [`std::time::Duration`] from the number of weeks.
fn std_weeks(self) -> StdDuration;
}
impl NumericalStdDuration for u64 {
fn std_nanoseconds(self) -> StdDuration {
StdDuration::from_nanos(self)
}
fn std_microseconds(self) -> StdDuration {
StdDuration::from_micros(self)
}
fn std_milliseconds(self) -> StdDuration {
StdDuration::from_millis(self)
}
fn std_seconds(self) -> StdDuration {
StdDuration::from_secs(self)
}
/// # Panics
///
/// This may panic if an overflow occurs.
fn std_minutes(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per(Minute).extend())
.expect("overflow constructing `time::Duration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
fn std_hours(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per(Hour).extend())
.expect("overflow constructing `time::Duration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
fn std_days(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per(Day).extend())
.expect("overflow constructing `time::Duration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
fn std_weeks(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per(Week).extend())
.expect("overflow constructing `time::Duration`"),
)
}
}
impl NumericalStdDuration for f64 {
/// # Panics
///
/// This will panic if self is negative.
fn std_nanoseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos(self as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_microseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Microsecond) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_milliseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Millisecond) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_seconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Second) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_minutes(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Minute) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_hours(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Hour) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_days(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Day) as Self) as _)
}
/// # Panics
///
/// This will panic if self is negative.
fn std_weeks(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per(Week) as Self) as _)
}
}

View file

@ -5,6 +5,19 @@ use alloc::string::String;
#[cfg(feature = "alloc")]
use core::fmt;
/// A complete description of how to format and parse a type.
///
/// This alias exists for backwards-compatibility. It is recommended to use `BorrowedFormatItem`
/// for clarity, as it is more explicit that the data is borrowed rather than owned.
#[cfg(doc)]
#[deprecated(
since = "0.3.35",
note = "use `BorrowedFormatItem` instead for clarity"
)]
pub type FormatItem<'a> = BorrowedFormatItem<'a>;
#[cfg(not(doc))]
pub use self::BorrowedFormatItem as FormatItem;
use crate::error;
use crate::format_description::Component;
@ -15,8 +28,8 @@ use crate::format_description::Component;
pub enum BorrowedFormatItem<'a> {
/// Bytes that are formatted as-is.
///
/// **Note**: If you call the `format` method that returns a `String`, these bytes will be
/// passed through `String::from_utf8_lossy`.
/// **Note**: These bytes **should** be UTF-8, but are not required to be. The value is passed
/// through `String::from_utf8_lossy` when necessary.
Literal(&'a [u8]),
/// A minimal representation of a single non-literal item.
Component(Component),

View file

@ -38,4 +38,7 @@ pub enum Component {
Ignore(modifier::Ignore),
/// A Unix timestamp.
UnixTimestamp(modifier::UnixTimestamp),
/// The end of input. Parsing this component will fail if there is any input remaining. This
/// component neither affects formatting nor consumes any input when parsing.
End(modifier::End),
}

View file

@ -15,7 +15,9 @@ mod owned_format_item;
#[cfg(feature = "alloc")]
mod parse;
pub use borrowed_format_item::BorrowedFormatItem as FormatItem;
pub use borrowed_format_item::BorrowedFormatItem;
#[allow(deprecated)]
pub use borrowed_format_item::FormatItem;
#[cfg(feature = "alloc")]
pub use owned_format_item::OwnedFormatItem;

View file

@ -279,6 +279,13 @@ pub struct UnixTimestamp {
pub sign_is_mandatory: bool,
}
/// The end of input.
///
/// There is currently not customization for this modifier.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct End;
/// Generate the provided code if and only if `pub` is present.
macro_rules! if_pub {
(pub $(#[$attr:meta])*; $($x:tt)*) => {
@ -385,10 +392,10 @@ impl_const_default! {
/// Creates a modifier that indicates the stringified value contains [one or more
/// digits](SubsecondDigits::OneOrMore).
@pub Subsecond => Self { digits: SubsecondDigits::OneOrMore };
/// Creates a modifier that indicates the value uses the `+` sign for all positive values
/// and is [padded with zeroes](Padding::Zero).
/// Creates a modifier that indicates the value only uses a sign for negative values and is
/// [padded with zeroes](Padding::Zero).
@pub OffsetHour => Self {
sign_is_mandatory: true,
sign_is_mandatory: false,
padding: Padding::Zero,
};
/// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
@ -406,4 +413,6 @@ impl_const_default! {
precision: UnixTimestampPrecision::Second,
sign_is_mandatory: false,
};
/// Creates a modifier used to represent the end of input.
@pub End => End;
}

View file

@ -6,7 +6,7 @@ use alloc::vec::Vec;
use core::fmt;
use crate::error;
use crate::format_description::{Component, FormatItem};
use crate::format_description::{BorrowedFormatItem, Component};
/// A complete description of how to format and parse a type.
#[non_exhaustive]
@ -14,8 +14,8 @@ use crate::format_description::{Component, FormatItem};
pub enum OwnedFormatItem {
/// Bytes that are formatted as-is.
///
/// **Note**: If you call the `format` method that returns a `String`, these bytes will be
/// passed through `String::from_utf8_lossy`.
/// **Note**: These bytes **should** be UTF-8, but are not required to be. The value is passed
/// through `String::from_utf8_lossy` when necessary.
Literal(Box<[u8]>),
/// A minimal representation of a single non-literal item.
Component(Component),
@ -46,18 +46,20 @@ impl fmt::Debug for OwnedFormatItem {
}
// region: conversions from FormatItem
impl From<FormatItem<'_>> for OwnedFormatItem {
fn from(item: FormatItem<'_>) -> Self {
impl From<BorrowedFormatItem<'_>> for OwnedFormatItem {
fn from(item: BorrowedFormatItem<'_>) -> Self {
(&item).into()
}
}
impl From<&FormatItem<'_>> for OwnedFormatItem {
fn from(item: &FormatItem<'_>) -> Self {
impl From<&BorrowedFormatItem<'_>> for OwnedFormatItem {
fn from(item: &BorrowedFormatItem<'_>) -> Self {
match item {
FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
FormatItem::Component(component) => Self::Component(*component),
FormatItem::Compound(compound) => Self::Compound(
BorrowedFormatItem::Literal(literal) => {
Self::Literal(literal.to_vec().into_boxed_slice())
}
BorrowedFormatItem::Component(component) => Self::Component(*component),
BorrowedFormatItem::Compound(compound) => Self::Compound(
compound
.iter()
.cloned()
@ -65,8 +67,8 @@ impl From<&FormatItem<'_>> for OwnedFormatItem {
.collect::<Vec<_>>()
.into_boxed_slice(),
),
FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
FormatItem::First(items) => Self::First(
BorrowedFormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
BorrowedFormatItem::First(items) => Self::First(
items
.iter()
.cloned()
@ -78,13 +80,13 @@ impl From<&FormatItem<'_>> for OwnedFormatItem {
}
}
impl From<Vec<FormatItem<'_>>> for OwnedFormatItem {
fn from(items: Vec<FormatItem<'_>>) -> Self {
impl From<Vec<BorrowedFormatItem<'_>>> for OwnedFormatItem {
fn from(items: Vec<BorrowedFormatItem<'_>>) -> Self {
items.as_slice().into()
}
}
impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem {
impl<'a, T: AsRef<[BorrowedFormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem {
fn from(items: &T) -> Self {
Self::Compound(
items

View file

@ -6,6 +6,7 @@ use alloc::vec::Vec;
use core::iter;
use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused};
use crate::internal_macros::bug;
/// One part of a complete format description.
pub(super) enum Item<'a> {

View file

@ -6,6 +6,7 @@ use core::num::NonZeroU16;
use core::str::{self, FromStr};
use super::{ast, unused, Error, Span, Spanned};
use crate::internal_macros::bug;
/// Parse an AST iterator into a sequence of format items.
pub(super) fn parse<'a>(
@ -101,7 +102,7 @@ impl Item<'_> {
}
}
impl<'a> TryFrom<Item<'a>> for crate::format_description::FormatItem<'a> {
impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> {
type Error = Error;
fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
@ -148,14 +149,9 @@ impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
fn from(items: Box<[Item<'a>]>) -> Self {
let items = items.into_vec();
if items.len() == 1 {
if let Ok([item]) = <[_; 1]>::try_from(items) {
item.into()
} else {
bug!("the length was just checked to be 1")
}
} else {
Self::Compound(items.into_iter().map(Self::from).collect())
match <[_; 1]>::try_from(items) {
Ok([item]) => item.into(),
Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
}
}
}
@ -190,6 +186,8 @@ macro_rules! component_definition {
_component_span: Span,
) -> Result<Self, Error>
{
// rustc will complain if the modifier is empty.
#[allow(unused_mut)]
let mut this = Self {
$($field: None),*
};
@ -280,6 +278,7 @@ component_definition! {
Day = "day" {
padding = "padding": Option<Padding> => padding,
},
End = "end" {},
Hour = "hour" {
padding = "padding": Option<Padding> => padding,
base = "repr": Option<HourBase> => is_12_hour_clock,

View file

@ -7,7 +7,7 @@ use super::{unused, Error, Location, Spanned, SpannedValue};
/// An iterator over the lexed tokens.
pub(super) struct Lexed<I: Iterator> {
/// The internal iterator.
iter: core::iter::Peekable<I>,
iter: iter::Peekable<I>,
}
impl<I: Iterator> Iterator for Lexed<I> {

View file

@ -3,6 +3,8 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::{error, format_description};
/// A helper macro to make version restrictions simpler to read and write.
macro_rules! version {
($range:expr) => {
@ -40,8 +42,7 @@ impl<const N: usize> Version<N> {
/// `parse_borrowed`.
pub fn parse(
s: &str,
) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
{
) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
parse_borrowed::<1>(s)
}
@ -52,8 +53,7 @@ pub fn parse(
/// description is provided as the const parameter. **It is recommended to use version 2.**
pub fn parse_borrowed<const VERSION: usize>(
s: &str,
) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
{
) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
validate_version!(VERSION);
let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
let ast = ast::parse::<_, VERSION>(&mut lexed);
@ -75,14 +75,12 @@ pub fn parse_borrowed<const VERSION: usize>(
/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
pub fn parse_owned<const VERSION: usize>(
s: &str,
) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> {
) -> Result<format_description::OwnedFormatItem, error::InvalidFormatDescription> {
validate_version!(VERSION);
let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
let ast = ast::parse::<_, VERSION>(&mut lexed);
let format_items = format_item::parse(ast);
let items = format_items
.map(|res| res.map(Into::into))
.collect::<Result<Box<_>, _>>()?;
let items = format_items.collect::<Result<Box<_>, _>>()?;
Ok(items.into())
}
@ -222,10 +220,10 @@ struct Error {
/// The internal error.
_inner: Unused<ErrorInner>,
/// The error needed for interoperability with the rest of `time`.
public: crate::error::InvalidFormatDescription,
public: error::InvalidFormatDescription,
}
impl From<Error> for crate::error::InvalidFormatDescription {
impl From<Error> for error::InvalidFormatDescription {
fn from(error: Error) -> Self {
error.public
}
@ -239,7 +237,6 @@ impl From<Error> for crate::error::InvalidFormatDescription {
struct Unused<T>(core::marker::PhantomData<T>);
/// Indicate that a value is currently unused.
#[allow(clippy::missing_const_for_fn)] // false positive
fn unused<T>(_: T) -> Unused<T> {
Unused(core::marker::PhantomData)
}

View file

@ -4,23 +4,9 @@ mod adt_hack;
use core::num::NonZeroU8;
pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig};
/// A configuration for [`Iso8601`] that only parses values.
const PARSING_ONLY: EncodedConfig = Config {
formatted_components: FormattedComponents::None,
use_separators: false,
year_is_six_digits: false,
date_kind: DateKind::Calendar,
time_precision: TimePrecision::Hour {
decimal_digits: None,
},
offset_precision: OffsetPrecision::Hour,
}
.encode();
/// The default configuration for [`Iso8601`].
const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();
#[doc(hidden, no_inline)]
pub use self::adt_hack::DoNotRelyOnWhatThisIs;
pub use self::adt_hack::EncodedConfig;
/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html).
///
@ -43,7 +29,7 @@ const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Iso8601<const CONFIG: EncodedConfig = DEFAULT_CONFIG>;
pub struct Iso8601<const CONFIG: EncodedConfig = { Config::DEFAULT.encode() }>;
impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@ -53,7 +39,18 @@ impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
}
}
impl Iso8601<DEFAULT_CONFIG> {
/// Define associated constants for `Iso8601`.
macro_rules! define_assoc_consts {
($($(#[$doc:meta])* $vis:vis const $const_name:ident = $format:expr;)*) => {$(
const $const_name: EncodedConfig = $format.encode();
impl Iso8601<$const_name> {
$(#[$doc])*
$vis const $const_name: Self = Self;
}
)*};
}
define_assoc_consts! {
/// An [`Iso8601`] with the default configuration.
///
/// The following is the default behavior:
@ -66,15 +63,29 @@ impl Iso8601<DEFAULT_CONFIG> {
/// - The time has precision to the second and nine decimal digits.
/// - The UTC offset has precision to the minute.
///
/// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create
/// a custom configuration.
pub const DEFAULT: Self = Self;
}
impl Iso8601<PARSING_ONLY> {
/// If you need different behavior, use another associated constant. For full customization, use
/// [`Config::DEFAULT`] and [`Config`]'s methods to create a custom configuration.
pub const DEFAULT = Config::DEFAULT;
/// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
/// unspecified behavior.
pub const PARSING: Self = Self;
pub const PARSING = Config::PARSING;
/// An [`Iso8601`] that handles only the date, but is otherwise the same as [`Config::DEFAULT`].
pub const DATE = Config::DEFAULT.set_formatted_components(FormattedComponents::Date);
/// An [`Iso8601`] that handles only the time, but is otherwise the same as [`Config::DEFAULT`].
pub const TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::Time);
/// An [`Iso8601`] that handles only the UTC offset, but is otherwise the same as
/// [`Config::DEFAULT`].
pub const OFFSET = Config::DEFAULT.set_formatted_components(FormattedComponents::Offset);
/// An [`Iso8601`] that handles the date and time, but is otherwise the same as
/// [`Config::DEFAULT`].
pub const DATE_TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::DateTime);
/// An [`Iso8601`] that handles the date, time, and UTC offset. This is the same as
/// [`Config::DEFAULT`].
pub const DATE_TIME_OFFSET = Config::DEFAULT;
/// An [`Iso8601`] that handles the time and UTC offset, but is otherwise the same as
/// [`Config::DEFAULT`].
pub const TIME_OFFSET = Config::DEFAULT
.set_formatted_components(FormattedComponents::TimeOffset);
}
/// Which components to format.
@ -114,19 +125,19 @@ pub enum TimePrecision {
/// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
/// specified number of decimal digits, if any.
Hour {
#[allow(clippy::missing_docs_in_private_items)]
#[allow(missing_docs)]
decimal_digits: Option<NonZeroU8>,
},
/// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
/// number of decimal digits, if any.
Minute {
#[allow(clippy::missing_docs_in_private_items)]
#[allow(missing_docs)]
decimal_digits: Option<NonZeroU8>,
},
/// Format the hour, minute, and second. Nanoseconds will be represented with the specified
/// number of decimal digits, if any.
Second {
#[allow(clippy::missing_docs_in_private_items)]
#[allow(missing_docs)]
decimal_digits: Option<NonZeroU8>,
},
}
@ -186,6 +197,19 @@ impl Config {
offset_precision: OffsetPrecision::Minute,
};
/// A configuration that can only be used for parsing. Using this to format a value is
/// unspecified behavior.
const PARSING: Self = Self {
formatted_components: FormattedComponents::None,
use_separators: false,
year_is_six_digits: false,
date_kind: DateKind::Calendar,
time_precision: TimePrecision::Hour {
decimal_digits: None,
},
offset_precision: OffsetPrecision::Hour,
};
/// Set whether the format the date, time, and/or UTC offset.
pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
Self {

View file

@ -49,11 +49,10 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
}
impl Config {
/// Encode the configuration, permitting it to be used as a const parameter of
/// [`Iso8601`](super::Iso8601).
/// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`].
///
/// The value returned by this method must only be used as a const parameter to
/// [`Iso8601`](super::Iso8601). Any other usage is unspecified behavior.
/// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any
/// other usage is unspecified behavior.
pub const fn encode(&self) -> EncodedConfig {
let mut bytes = [0; EncodedConfig::BITS as usize / 8];

View file

@ -1,11 +1,15 @@
//! A trait that can be used to format an item from its components.
use alloc::string::String;
use alloc::vec::Vec;
use core::ops::Deref;
use std::io;
use num_conv::prelude::*;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::{FormatItem, OwnedFormatItem};
use crate::format_description::{BorrowedFormatItem, OwnedFormatItem};
use crate::formatting::{
format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
};
@ -19,8 +23,8 @@ use crate::{error, Date, Time, UtcOffset};
/// a String from their data. See the respective methods for usage examples.
#[cfg_attr(__time_03_docs, doc(notable_trait))]
pub trait Formattable: sealed::Sealed {}
impl Formattable for FormatItem<'_> {}
impl Formattable for [FormatItem<'_>] {}
impl Formattable for BorrowedFormatItem<'_> {}
impl Formattable for [BorrowedFormatItem<'_>] {}
impl Formattable for OwnedFormatItem {}
impl Formattable for [OwnedFormatItem] {}
impl Formattable for Rfc3339 {}
@ -59,7 +63,7 @@ mod sealed {
}
// region: custom formats
impl<'a> sealed::Sealed for FormatItem<'a> {
impl sealed::Sealed for BorrowedFormatItem<'_> {
fn format_into(
&self,
output: &mut impl io::Write,
@ -80,7 +84,7 @@ impl<'a> sealed::Sealed for FormatItem<'a> {
}
}
impl<'a> sealed::Sealed for [FormatItem<'a>] {
impl sealed::Sealed for [BorrowedFormatItem<'_>] {
fn format_into(
&self,
output: &mut impl io::Write,
@ -175,14 +179,17 @@ impl sealed::Sealed for Rfc2822 {
bytes += write(
output,
&WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
&WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
)?;
bytes += write(output, b", ")?;
bytes += format_number_pad_zero::<2>(output, day)?;
bytes += write(output, b" ")?;
bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?;
bytes += write(
output,
&MONTH_NAMES[u8::from(month).extend::<usize>() - 1][..3],
)?;
bytes += write(output, b" ")?;
bytes += format_number_pad_zero::<4>(output, year as u32)?;
bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
bytes += write(output, b" ")?;
bytes += format_number_pad_zero::<2>(output, time.hour())?;
bytes += write(output, b":")?;
@ -217,13 +224,16 @@ impl sealed::Sealed for Rfc3339 {
if !(0..10_000).contains(&year) {
return Err(error::Format::InvalidComponent("year"));
}
if offset.whole_hours().unsigned_abs() > 23 {
return Err(error::Format::InvalidComponent("offset_hour"));
}
if offset.seconds_past_minute() != 0 {
return Err(error::Format::InvalidComponent("offset_second"));
}
bytes += format_number_pad_zero::<4>(output, year as u32)?;
bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
bytes += write(output, b"-")?;
bytes += format_number_pad_zero::<2>(output, date.month() as u8)?;
bytes += format_number_pad_zero::<2>(output, u8::from(date.month()))?;
bytes += write(output, b"-")?;
bytes += format_number_pad_zero::<2>(output, date.day())?;
bytes += write(output, b"T")?;

View file

@ -2,6 +2,8 @@
use std::io;
use num_conv::prelude::*;
use crate::convert::*;
use crate::format_description::well_known::iso8601::{
DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
@ -26,10 +28,10 @@ pub(super) fn format_date<const CONFIG: EncodedConfig>(
} else if !(0..=9999).contains(&year) {
return Err(error::Format::InvalidComponent("year"));
} else {
bytes += format_number_pad_zero::<4>(output, year as u32)?;
bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
}
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
bytes += format_number_pad_zero::<2>(output, month as u8)?;
bytes += format_number_pad_zero::<2>(output, u8::from(month))?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
bytes += format_number_pad_zero::<2>(output, day)?;
}
@ -41,7 +43,7 @@ pub(super) fn format_date<const CONFIG: EncodedConfig>(
} else if !(0..=9999).contains(&year) {
return Err(error::Format::InvalidComponent("year"));
} else {
bytes += format_number_pad_zero::<4>(output, year as u32)?;
bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
}
bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?;
bytes += format_number_pad_zero::<2>(output, week)?;
@ -56,7 +58,7 @@ pub(super) fn format_date<const CONFIG: EncodedConfig>(
} else if !(0..=9999).contains(&year) {
return Err(error::Format::InvalidComponent("year"));
} else {
bytes += format_number_pad_zero::<4>(output, year as u32)?;
bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
}
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
bytes += format_number_pad_zero::<3>(output, day)?;
@ -85,17 +87,17 @@ pub(super) fn format_time<const CONFIG: EncodedConfig>(
match Iso8601::<CONFIG>::TIME_PRECISION {
TimePrecision::Hour { decimal_digits } => {
let hours = (hours as f64)
+ (minutes as f64) / Minute.per(Hour) as f64
+ (seconds as f64) / Second.per(Hour) as f64
+ (nanoseconds as f64) / Nanosecond.per(Hour) as f64;
+ (minutes as f64) / Minute::per(Hour) as f64
+ (seconds as f64) / Second::per(Hour) as f64
+ (nanoseconds as f64) / Nanosecond::per(Hour) as f64;
format_float(output, hours, 2, decimal_digits)?;
}
TimePrecision::Minute { decimal_digits } => {
bytes += format_number_pad_zero::<2>(output, hours)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
let minutes = (minutes as f64)
+ (seconds as f64) / Second.per(Minute) as f64
+ (nanoseconds as f64) / Nanosecond.per(Minute) as f64;
+ (seconds as f64) / Second::per(Minute) as f64
+ (nanoseconds as f64) / Nanosecond::per(Minute) as f64;
bytes += format_float(output, minutes, 2, decimal_digits)?;
}
TimePrecision::Second { decimal_digits } => {
@ -103,7 +105,7 @@ pub(super) fn format_time<const CONFIG: EncodedConfig>(
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
bytes += format_number_pad_zero::<2>(output, minutes)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
let seconds = (seconds as f64) + (nanoseconds as f64) / Nanosecond.per(Second) as f64;
let seconds = (seconds as f64) + (nanoseconds as f64) / Nanosecond::per(Second) as f64;
bytes += format_float(output, seconds, 2, decimal_digits)?;
}
}

View file

@ -2,12 +2,14 @@
pub(crate) mod formattable;
mod iso8601;
use core::num::NonZeroU8;
use std::io;
use num_conv::prelude::*;
pub use self::formattable::Formattable;
use crate::convert::*;
use crate::ext::DigitCount;
use crate::format_description::{modifier, Component};
use crate::{error, Date, OffsetDateTime, Time, UtcOffset};
@ -38,86 +40,6 @@ const WEEKDAY_NAMES: [&[u8]; 7] = [
b"Sunday",
];
// region: extension trait
/// A trait that indicates the formatted width of the value can be determined.
///
/// Note that this should not be implemented for any signed integers. This forces the caller to
/// write the sign if desired.
pub(crate) trait DigitCount {
/// The number of digits in the stringified value.
fn num_digits(self) -> u8;
}
impl DigitCount for u8 {
fn num_digits(self) -> u8 {
// Using a lookup table as with u32 is *not* faster in standalone benchmarks.
if self < 10 {
1
} else if self < 100 {
2
} else {
3
}
}
}
impl DigitCount for u16 {
fn num_digits(self) -> u8 {
// Using a lookup table as with u32 is *not* faster in standalone benchmarks.
if self < 10 {
1
} else if self < 100 {
2
} else if self < 1_000 {
3
} else if self < 10_000 {
4
} else {
5
}
}
}
impl DigitCount for u32 {
fn num_digits(self) -> u8 {
/// Lookup table
const TABLE: &[u64] = &[
0x0001_0000_0000,
0x0001_0000_0000,
0x0001_0000_0000,
0x0001_FFFF_FFF6,
0x0002_0000_0000,
0x0002_0000_0000,
0x0002_FFFF_FF9C,
0x0003_0000_0000,
0x0003_0000_0000,
0x0003_FFFF_FC18,
0x0004_0000_0000,
0x0004_0000_0000,
0x0004_0000_0000,
0x0004_FFFF_D8F0,
0x0005_0000_0000,
0x0005_0000_0000,
0x0005_FFFE_7960,
0x0006_0000_0000,
0x0006_0000_0000,
0x0006_FFF0_BDC0,
0x0007_0000_0000,
0x0007_0000_0000,
0x0007_0000_0000,
0x0007_FF67_6980,
0x0008_0000_0000,
0x0008_0000_0000,
0x0008_FA0A_1F00,
0x0009_0000_0000,
0x0009_0000_0000,
0x0009_C465_3600,
0x000A_0000_0000,
0x000A_0000_0000,
];
((self as u64 + TABLE[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _
}
}
// endregion extension trait
/// Write all bytes to the output, returning the number of bytes written.
pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
output.write_all(bytes)?;
@ -151,14 +73,14 @@ pub(crate) fn format_float(
) -> io::Result<usize> {
match digits_after_decimal {
Some(digits_after_decimal) => {
let digits_after_decimal = digits_after_decimal.get() as usize;
let width = digits_before_decimal as usize + 1 + digits_after_decimal;
let digits_after_decimal = digits_after_decimal.get().extend();
let width = digits_before_decimal.extend::<usize>() + 1 + digits_after_decimal;
write!(output, "{value:0>width$.digits_after_decimal$}")?;
Ok(width)
}
None => {
let value = value.trunc() as u64;
let width = digits_before_decimal as usize;
let width = digits_before_decimal.extend();
write!(output, "{value:0>width$}")?;
Ok(width)
}
@ -250,7 +172,19 @@ pub(crate) fn format_component(
(UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => {
fmt_unix_timestamp(output, date, time, offset, modifier)?
}
_ => return Err(error::Format::InsufficientTypeInformation),
(End(modifier::End {}), ..) => 0,
// This is functionally the same as a wildcard arm, but it will cause an error if a new
// component is added. This is to avoid a bug where a new component, the code compiles, and
// formatting fails.
// Allow unreachable patterns because some branches may be fully matched above.
#[allow(unreachable_patterns)]
(
Day(_) | Month(_) | Ordinal(_) | Weekday(_) | WeekNumber(_) | Year(_) | Hour(_)
| Minute(_) | Period(_) | Second(_) | Subsecond(_) | OffsetHour(_) | OffsetMinute(_)
| OffsetSecond(_) | Ignore(_) | UnixTimestamp(_) | End(_),
..,
) => return Err(error::Format::InsufficientTypeInformation),
})
}
@ -275,9 +209,17 @@ fn fmt_month(
}: modifier::Month,
) -> Result<usize, io::Error> {
match repr {
modifier::MonthRepr::Numerical => format_number::<2>(output, date.month() as u8, padding),
modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]),
modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]),
modifier::MonthRepr::Numerical => {
format_number::<2>(output, u8::from(date.month()), padding)
}
modifier::MonthRepr::Long => write(
output,
MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1],
),
modifier::MonthRepr::Short => write(
output,
&MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1][..3],
),
}
}
@ -303,20 +245,20 @@ fn fmt_weekday(
match repr {
modifier::WeekdayRepr::Short => write(
output,
&WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
&WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
),
modifier::WeekdayRepr::Long => write(
output,
WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize],
WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()],
),
modifier::WeekdayRepr::Sunday => format_number::<1>(
output,
date.weekday().number_days_from_sunday() + one_indexed as u8,
date.weekday().number_days_from_sunday() + u8::from(one_indexed),
modifier::Padding::None,
),
modifier::WeekdayRepr::Monday => format_number::<1>(
output,
date.weekday().number_days_from_monday() + one_indexed as u8,
date.weekday().number_days_from_monday() + u8::from(one_indexed),
modifier::Padding::None,
),
}
@ -515,10 +457,7 @@ fn fmt_unix_timestamp(
sign_is_mandatory,
}: modifier::UnixTimestamp,
) -> Result<usize, io::Error> {
let date_time = date
.with_time(time)
.assume_offset(offset)
.to_offset(UtcOffset::UTC);
let date_time = OffsetDateTime::new_in_offset(date, time, offset).to_offset(UtcOffset::UTC);
if date_time < OffsetDateTime::UNIX_EPOCH {
write(output, b"-")?;
@ -532,11 +471,15 @@ fn fmt_unix_timestamp(
}
modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none(
output,
(date_time.unix_timestamp_nanos() / Nanosecond.per(Millisecond) as i128).unsigned_abs(),
(date_time.unix_timestamp_nanos()
/ Nanosecond::per(Millisecond).cast_signed().extend::<i128>())
.unsigned_abs(),
),
modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none(
output,
(date_time.unix_timestamp_nanos() / Nanosecond.per(Microsecond) as i128).unsigned_abs(),
(date_time.unix_timestamp_nanos()
/ Nanosecond::per(Microsecond).cast_signed().extend::<i128>())
.unsigned_abs(),
),
modifier::UnixTimestampPrecision::Nanosecond => {
format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs())

View file

@ -1,11 +1,14 @@
//! The [`Instant`] struct and its associated `impl`s.
#![allow(deprecated)]
use core::borrow::Borrow;
use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use core::ops::{Add, Sub};
use core::time::Duration as StdDuration;
use std::time::Instant as StdInstant;
use crate::internal_macros::{impl_add_assign, impl_sub_assign};
use crate::Duration;
/// A measurement of a monotonically non-decreasing clock. Opaque and useful only with [`Duration`].
@ -25,6 +28,7 @@ use crate::Duration;
///
/// This implementation allows for operations with signed [`Duration`]s, but is otherwise identical
/// to [`std::time::Instant`].
#[deprecated(since = "0.3.35", note = "import `time::ext::InstantExt` instead")]
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(pub StdInstant);
@ -34,6 +38,7 @@ impl Instant {
/// Returns an `Instant` corresponding to "now".
///
/// ```rust
/// # #![allow(deprecated)]
/// # use time::Instant;
/// println!("{:?}", Instant::now());
/// ```
@ -45,6 +50,7 @@ impl Instant {
/// be nonnegative if the instant is not synthetically created.
///
/// ```rust
/// # #![allow(deprecated)]
/// # use time::{Instant, ext::{NumericalStdDuration, NumericalDuration}};
/// # use std::thread;
/// let instant = Instant::now();
@ -62,6 +68,7 @@ impl Instant {
/// otherwise.
///
/// ```rust
/// # #![allow(deprecated)]
/// # use time::{Instant, ext::NumericalDuration};
/// let now = Instant::now();
/// assert_eq!(now.checked_add(5.seconds()), Some(now + 5.seconds()));
@ -83,6 +90,7 @@ impl Instant {
/// otherwise.
///
/// ```rust
/// # #![allow(deprecated)]
/// # use time::{Instant, ext::NumericalDuration};
/// let now = Instant::now();
/// assert_eq!(now.checked_sub(5.seconds()), Some(now - 5.seconds()));
@ -103,6 +111,7 @@ impl Instant {
/// Obtain the inner [`std::time::Instant`].
///
/// ```rust
/// # #![allow(deprecated)]
/// # use time::Instant;
/// let now = Instant::now();
/// assert_eq!(now.into_inner(), now.0);
@ -128,6 +137,9 @@ impl From<Instant> for StdInstant {
impl Sub for Instant {
type Output = Duration;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, other: Self) -> Self::Output {
match self.0.cmp(&other.0) {
Ordering::Equal => Duration::ZERO,

View file

@ -0,0 +1,200 @@
//! Macros for use within the library. They are not publicly available.
/// Helper macro for easily implementing `OpAssign`.
macro_rules! __impl_assign {
($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$(
#[allow(unused_qualifications)]
$(#[$attr])*
impl core::ops::$op<$t> for $target {
fn $fn(&mut self, rhs: $t) {
*self = *self $sym rhs;
}
}
)+};
}
/// Implement `AddAssign` for the provided types.
macro_rules! impl_add_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
$crate::internal_macros::__impl_assign!(
+ AddAssign add_assign $target : $($(#[$attr])* $t),+
);
};
}
/// Implement `SubAssign` for the provided types.
macro_rules! impl_sub_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
$crate::internal_macros::__impl_assign!(
- SubAssign sub_assign $target : $($(#[$attr])* $t),+
);
};
}
/// Implement `MulAssign` for the provided types.
macro_rules! impl_mul_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
$crate::internal_macros::__impl_assign!(
* MulAssign mul_assign $target : $($(#[$attr])* $t),+
);
};
}
/// Implement `DivAssign` for the provided types.
macro_rules! impl_div_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
$crate::internal_macros::__impl_assign!(
/ DivAssign div_assign $target : $($(#[$attr])* $t),+
);
};
}
/// Division of integers, rounding the resulting value towards negative infinity.
macro_rules! div_floor {
($a:expr, $b:expr) => {{
let _a = $a;
let _b = $b;
let (_quotient, _remainder) = (_a / _b, _a % _b);
if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) {
_quotient - 1
} else {
_quotient
}
}};
}
/// Cascade an out-of-bounds value.
macro_rules! cascade {
(@ordinal ordinal) => {};
(@year year) => {};
// Cascade an out-of-bounds value from "from" to "to".
($from:ident in $min:literal.. $max:expr => $to:tt) => {
#[allow(unused_comparisons, unused_assignments)]
let min = $min;
let max = $max;
if $from >= max {
$from -= max - min;
$to += 1;
} else if $from < min {
$from += max - min;
$to -= 1;
}
};
// Special case the ordinal-to-year cascade, as it has different behavior.
($ordinal:ident => $year:ident) => {
// We need to actually capture the idents. Without this, macro hygiene causes errors.
cascade!(@ordinal $ordinal);
cascade!(@year $year);
#[allow(unused_assignments)]
if $ordinal > crate::util::days_in_year($year) as i16 {
$ordinal -= crate::util::days_in_year($year) as i16;
$year += 1;
} else if $ordinal < 1 {
$year -= 1;
$ordinal += crate::util::days_in_year($year) as i16;
}
};
}
/// Constructs a ranged integer, returning a `ComponentRange` error if the value is out of range.
macro_rules! ensure_ranged {
($type:ident : $value:ident) => {
match $type::new($value) {
Some(val) => val,
None => {
#[allow(trivial_numeric_casts)]
return Err(crate::error::ComponentRange {
name: stringify!($value),
minimum: $type::MIN.get() as _,
maximum: $type::MAX.get() as _,
value: $value as _,
conditional_range: false,
});
}
}
};
($type:ident : $value:ident $(as $as_type:ident)? * $factor:expr) => {
match ($value $(as $as_type)?).checked_mul($factor) {
Some(val) => match $type::new(val) {
Some(val) => val,
None => {
#[allow(trivial_numeric_casts)]
return Err(crate::error::ComponentRange {
name: stringify!($value),
minimum: $type::MIN.get() as i64 / $factor as i64,
maximum: $type::MAX.get() as i64 / $factor as i64,
value: $value as _,
conditional_range: false,
});
}
},
None => {
return Err(crate::error::ComponentRange {
name: stringify!($value),
minimum: $type::MIN.get() as i64 / $factor as i64,
maximum: $type::MAX.get() as i64 / $factor as i64,
value: $value as _,
conditional_range: false,
});
}
}
};
}
/// Try to unwrap an expression, returning if not possible.
///
/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is
/// usable in `const` contexts.
macro_rules! const_try {
($e:expr) => {
match $e {
Ok(value) => value,
Err(error) => return Err(error),
}
};
}
/// Try to unwrap an expression, returning if not possible.
///
/// This is similar to the `?` operator, but is usable in `const` contexts.
macro_rules! const_try_opt {
($e:expr) => {
match $e {
Some(value) => value,
None => return None,
}
};
}
/// Try to unwrap an expression, panicking if not possible.
///
/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
macro_rules! expect_opt {
($e:expr, $message:literal) => {
match $e {
Some(value) => value,
None => crate::expect_failed($message),
}
};
}
/// `unreachable!()`, but better.
#[cfg(any(feature = "formatting", feature = "parsing"))]
macro_rules! bug {
() => { compile_error!("provide an error message to help fix a possible bug") };
($descr:literal $($rest:tt)?) => {
panic!(concat!("internal error: ", $descr) $($rest)?)
}
}
#[cfg(any(feature = "formatting", feature = "parsing"))]
pub(crate) use bug;
pub(crate) use {
__impl_assign, cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt,
impl_add_assign, impl_div_assign, impl_mul_assign, impl_sub_assign,
};

View file

@ -52,14 +52,6 @@
//! Libraries should never enable this feature, as the decision of what format to use should be up
//! to the user.
//!
//! - `serde-well-known` (_implicitly enables `serde-human-readable`_)
//!
//! _This feature flag is deprecated and will be removed in a future breaking release. Use the
//! `serde-human-readable` feature instead._
//!
//! Enables support for serializing and deserializing well-known formats using serde's
//! [`#[with]` attribute](https://serde.rs/field-attrs.html#with).
//!
//! - `rand`
//!
//! Enables [rand](https://docs.rs/rand) support for all types.
@ -76,55 +68,7 @@
#![doc(html_playground_url = "https://play.rust-lang.org")]
#![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))]
#![cfg_attr(coverage_nightly, feature(no_coverage))]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(
anonymous_parameters,
clippy::all,
clippy::alloc_instead_of_core,
clippy::explicit_auto_deref,
clippy::obfuscated_if_else,
clippy::std_instead_of_core,
clippy::undocumented_unsafe_blocks,
illegal_floating_point_literal_pattern,
late_bound_lifetime_arguments,
path_statements,
patterns_in_fns_without_body,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_extern_crates,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links
)]
#![warn(
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::get_unwrap,
clippy::missing_docs_in_private_items,
clippy::nursery,
clippy::print_stdout,
clippy::todo,
clippy::unimplemented,
clippy::uninlined_format_args,
clippy::unnested_or_patterns,
clippy::unwrap_in_result,
clippy::unwrap_used,
clippy::use_debug,
deprecated_in_future,
missing_copy_implementations,
missing_debug_implementations,
unused_qualifications,
variant_size_differences
)]
#![allow(
clippy::redundant_pub_crate, // suggests bad style
clippy::option_if_let_else, // suggests terrible code
clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released
clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released
)]
#![no_std]
#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
#![doc(test(attr(deny(warnings))))]
@ -133,187 +77,10 @@
#[cfg(feature = "alloc")]
extern crate alloc;
// TODO(jhpratt) remove this after a while
#[cfg(unsound_local_offset)]
compile_error!(
"The `unsound_local_offset` flag was removed in time 0.3.18. If you need this functionality, \
see the `time::util::local_offset::set_soundness` function."
);
// region: macros
/// Helper macro for easily implementing `OpAssign`.
macro_rules! __impl_assign {
($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$(
#[allow(unused_qualifications)]
$(#[$attr])*
impl core::ops::$op<$t> for $target {
fn $fn(&mut self, rhs: $t) {
*self = *self $sym rhs;
}
}
)+};
}
/// Implement `AddAssign` for the provided types.
macro_rules! impl_add_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
__impl_assign!(+ AddAssign add_assign $target : $($(#[$attr])* $t),+);
};
}
/// Implement `SubAssign` for the provided types.
macro_rules! impl_sub_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
__impl_assign!(- SubAssign sub_assign $target : $($(#[$attr])* $t),+);
};
}
/// Implement `MulAssign` for the provided types.
macro_rules! impl_mul_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
__impl_assign!(* MulAssign mul_assign $target : $($(#[$attr])* $t),+);
};
}
/// Implement `DivAssign` for the provided types.
macro_rules! impl_div_assign {
($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
__impl_assign!(/ DivAssign div_assign $target : $($(#[$attr])* $t),+);
};
}
/// Division of integers, rounding the resulting value towards negative infinity.
macro_rules! div_floor {
($a:expr, $b:expr) => {{
let _a = $a;
let _b = $b;
let (_quotient, _remainder) = (_a / _b, _a % _b);
if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) {
_quotient - 1
} else {
_quotient
}
}};
}
/// Cascade an out-of-bounds value.
macro_rules! cascade {
(@ordinal ordinal) => {};
(@year year) => {};
// Cascade an out-of-bounds value from "from" to "to".
($from:ident in $min:literal.. $max:expr => $to:tt) => {
#[allow(unused_comparisons, unused_assignments)]
let min = $min;
let max = $max;
if $from >= max {
$from -= max - min;
$to += 1;
} else if $from < min {
$from += max - min;
$to -= 1;
}
};
// Special case the ordinal-to-year cascade, as it has different behavior.
($ordinal:ident => $year:ident) => {
// We need to actually capture the idents. Without this, macro hygiene causes errors.
cascade!(@ordinal $ordinal);
cascade!(@year $year);
#[allow(unused_assignments)]
if $ordinal > crate::util::days_in_year($year) as i16 {
$ordinal -= crate::util::days_in_year($year) as i16;
$year += 1;
} else if $ordinal < 1 {
$year -= 1;
$ordinal += crate::util::days_in_year($year) as i16;
}
};
}
/// Returns `Err(error::ComponentRange)` if the value is not in range.
macro_rules! ensure_value_in_range {
($value:ident in $start:expr => $end:expr) => {{
let _start = $start;
let _end = $end;
#[allow(trivial_numeric_casts, unused_comparisons)]
if $value < _start || $value > _end {
return Err(crate::error::ComponentRange {
name: stringify!($value),
minimum: _start as _,
maximum: _end as _,
value: $value as _,
conditional_range: false,
});
}
}};
($value:ident conditionally in $start:expr => $end:expr) => {{
let _start = $start;
let _end = $end;
#[allow(trivial_numeric_casts, unused_comparisons)]
if $value < _start || $value > _end {
return Err(crate::error::ComponentRange {
name: stringify!($value),
minimum: _start as _,
maximum: _end as _,
value: $value as _,
conditional_range: true,
});
}
}};
}
/// Try to unwrap an expression, returning if not possible.
///
/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is
/// usable in `const` contexts.
macro_rules! const_try {
($e:expr) => {
match $e {
Ok(value) => value,
Err(error) => return Err(error),
}
};
}
/// Try to unwrap an expression, returning if not possible.
///
/// This is similar to the `?` operator, but is usable in `const` contexts.
macro_rules! const_try_opt {
($e:expr) => {
match $e {
Some(value) => value,
None => return None,
}
};
}
/// Try to unwrap an expression, panicking if not possible.
///
/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
macro_rules! expect_opt {
($e:expr, $message:literal) => {
match $e {
Some(value) => value,
None => crate::expect_failed($message),
}
};
}
/// `unreachable!()`, but better.
macro_rules! bug {
() => { compile_error!("provide an error message to help fix a possible bug") };
($descr:literal $($rest:tt)?) => {
panic!(concat!("internal error: ", $descr) $($rest)?)
}
}
// endregion macros
#[cfg(feature = "std")]
extern crate std;
mod date;
mod date_time;
mod duration;
pub mod error;
pub mod ext;
@ -323,6 +90,7 @@ pub mod format_description;
pub mod formatting;
#[cfg(feature = "std")]
mod instant;
mod internal_macros;
#[cfg(feature = "macros")]
pub mod macros;
mod month;
@ -345,14 +113,13 @@ mod utc_offset;
pub mod util;
mod weekday;
// Not public yet.
use time_core::convert;
pub use time_core::convert;
pub use crate::date::Date;
use crate::date_time::DateTime;
pub use crate::duration::Duration;
pub use crate::error::Error;
#[cfg(feature = "std")]
#[allow(deprecated)]
pub use crate::instant::Instant;
pub use crate::month::Month;
pub use crate::offset_date_time::OffsetDateTime;

View file

@ -51,8 +51,8 @@ pub use time_macros::date;
pub use time_macros::datetime;
/// Equivalent of performing [`format_description::parse()`] at compile time.
///
/// Using the macro instead of the function results in a static slice rather than a [`Vec`],
/// such that it can be used in `#![no_alloc]` situations.
/// Using the macro instead of the function results in a static slice rather than a
/// [`Vec`](alloc::vec::Vec), such that it can be used in `#![no_alloc]` situations.
///
/// The resulting expression can be used in `const` or `static` declarations, and implements
/// the sealed traits required for both formatting and parsing.

View file

@ -4,25 +4,38 @@ use core::fmt;
use core::num::NonZeroU8;
use core::str::FromStr;
use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay};
use self::Month::*;
use crate::error;
/// Months of the year.
#[allow(clippy::missing_docs_in_private_items)] // variants
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Month {
#[allow(missing_docs)]
January = 1,
#[allow(missing_docs)]
February = 2,
#[allow(missing_docs)]
March = 3,
#[allow(missing_docs)]
April = 4,
#[allow(missing_docs)]
May = 5,
#[allow(missing_docs)]
June = 6,
#[allow(missing_docs)]
July = 7,
#[allow(missing_docs)]
August = 8,
#[allow(missing_docs)]
September = 9,
#[allow(missing_docs)]
October = 10,
#[allow(missing_docs)]
November = 11,
#[allow(missing_docs)]
December = 12,
}
@ -153,9 +166,35 @@ impl Month {
}
}
impl fmt::Display for Month {
mod private {
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct MonthMetadata;
}
use private::MonthMetadata;
impl SmartDisplay for Month {
type Metadata = MonthMetadata;
fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
match self {
January => Metadata::new(7, self, MonthMetadata),
February => Metadata::new(8, self, MonthMetadata),
March => Metadata::new(5, self, MonthMetadata),
April => Metadata::new(5, self, MonthMetadata),
May => Metadata::new(3, self, MonthMetadata),
June => Metadata::new(4, self, MonthMetadata),
July => Metadata::new(4, self, MonthMetadata),
August => Metadata::new(6, self, MonthMetadata),
September => Metadata::new(9, self, MonthMetadata),
October => Metadata::new(7, self, MonthMetadata),
November => Metadata::new(8, self, MonthMetadata),
December => Metadata::new(8, self, MonthMetadata),
}
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
f.pad(match self {
January => "January",
February => "February",
March => "March",
@ -172,6 +211,12 @@ impl fmt::Display for Month {
}
}
impl fmt::Display for Month {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self, f)
}
}
impl FromStr for Month {
type Err = error::InvalidVariant;

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@
pub(crate) mod rfc;
use num_conv::prelude::*;
use crate::format_description::modifier::Padding;
use crate::parsing::shim::{Integer, IntegerParseBytes};
use crate::parsing::ParsedItem;
@ -135,7 +137,7 @@ pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>(
None => break,
}
}
let pad_width = (orig_input.len() - input.len()) as u8;
let pad_width = (orig_input.len() - input.len()).truncate::<u8>();
orig_input = input;
for _ in 0..(N - pad_width) {

View file

@ -4,6 +4,8 @@
use core::num::{NonZeroU16, NonZeroU8};
use num_conv::prelude::*;
use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign};
use crate::parsing::ParsedItem;
use crate::{Month, Weekday};
@ -62,10 +64,10 @@ impl ExtendedKind {
pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> {
Some(match sign(input) {
Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| {
let val = val as i32;
let val = val.cast_signed();
if sign == b'-' { -val } else { val }
}),
None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _),
None => exactly_n_digits::<4, u32>(input)?.map(|val| val.cast_signed()),
})
}

View file

@ -2,6 +2,8 @@
use core::num::{NonZeroU16, NonZeroU8};
use num_conv::prelude::*;
use crate::convert::*;
use crate::format_description::modifier;
#[cfg(feature = "large-dates")]
@ -25,14 +27,14 @@ pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<Pars
let ParsedItem(input, year) =
n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
match sign {
Some(b'-') => Some(ParsedItem(input, -(year as i32))),
Some(b'-') => Some(ParsedItem(input, -year.cast_signed())),
None if modifiers.sign_is_mandatory || year >= 10_000 => None,
_ => Some(ParsedItem(input, year as i32)),
_ => Some(ParsedItem(input, year.cast_signed())),
}
}
modifier::YearRepr::LastTwo => {
Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32))
}
modifier::YearRepr::LastTwo => Some(
exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v.cast_signed()),
),
}
}
@ -243,11 +245,11 @@ pub(crate) fn parse_subsecond(
Nine => exactly_n_digits::<9, _>(input)?,
OneOrMore => {
let ParsedItem(mut input, mut value) =
any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000);
any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0') as u32 * multiplier;
value += (digit - b'0').extend::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
@ -269,9 +271,9 @@ pub(crate) fn parse_offset_hour(
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
match sign {
Some(b'-') => Some(ParsedItem(input, (-(hour as i8), true))),
Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, (hour as i8, false))),
_ => Some(ParsedItem(input, (hour.cast_signed(), false))),
}
}
@ -282,7 +284,7 @@ pub(crate) fn parse_offset_minute(
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_minute| offset_minute as _),
.map(|offset_minute| offset_minute.cast_signed()),
)
}
@ -293,7 +295,7 @@ pub(crate) fn parse_offset_second(
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_second| offset_second as _),
.map(|offset_second| offset_second.cast_signed()),
)
}
// endregion offset components
@ -304,7 +306,7 @@ pub(crate) fn parse_ignore(
modifiers: modifier::Ignore,
) -> Option<ParsedItem<'_, ()>> {
let modifier::Ignore { count } = modifiers;
let input = input.get((count.get() as usize)..)?;
let input = input.get((count.get().extend())..)?;
Some(ParsedItem(input, ()))
}
@ -315,19 +317,30 @@ pub(crate) fn parse_unix_timestamp(
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) = match modifiers.precision {
modifier::UnixTimestampPrecision::Second => {
n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond.per(Second) as u128)
}
modifier::UnixTimestampPrecision::Second => n_to_m_digits::<1, 14, u128>(input)?
.map(|val| val * Nanosecond::per(Second).extend::<u128>()),
modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
.map(|val| val * Nanosecond.per(Millisecond) as u128),
.map(|val| val * Nanosecond::per(Millisecond).extend::<u128>()),
modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
.map(|val| val * Nanosecond.per(Microsecond) as u128),
.map(|val| val * Nanosecond::per(Microsecond).extend::<u128>()),
modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
};
match sign {
Some(b'-') => Some(ParsedItem(input, -(nano_timestamp as i128))),
Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp as _)),
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the `end` component, which represents the end of input. If any input is remaining, `None`
/// is returned.
pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
let modifier::End {} = end;
if input.is_empty() {
Some(ParsedItem(input, ()))
} else {
None
}
}

View file

@ -1,5 +1,7 @@
//! Parse parts of an ISO 8601-formatted value.
use num_conv::prelude::*;
use crate::convert::*;
use crate::error;
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
@ -34,7 +36,7 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
};
let mut ret_error = match (|| {
let parsed_month_day = (|| {
let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
if extended_kind.is_extended() {
input = ascii_char::<b'-'>(input)
@ -43,7 +45,8 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
}
let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
Ok(ParsedItem(input, (month, day)))
})() {
})();
let mut ret_error = match parsed_month_day {
Ok(ParsedItem(input, (month, day))) => {
*parsed = parsed
.with_year(year)
@ -67,7 +70,7 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
return Ok(input);
}
match (|| {
let parsed_week_weekday = (|| {
let input = ascii_char::<b'W'>(input)
.ok_or((false, InvalidLiteral))?
.into_inner();
@ -81,7 +84,8 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
let ParsedItem(input, weekday) =
dayk(input).ok_or((true, InvalidComponent("weekday")))?;
Ok(ParsedItem(input, (week, weekday)))
})() {
})();
match parsed_week_weekday {
Ok(ParsedItem(input, (week, weekday))) => {
*parsed = parsed
.with_iso_year(year)
@ -125,16 +129,16 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
*parsed = parsed
.with_hour_24(hour)
.ok_or(InvalidComponent("hour"))?
.with_minute((fractional_part * Second.per(Minute) as f64) as _)
.with_minute((fractional_part * Second::per(Minute) as f64) as _)
.ok_or(InvalidComponent("minute"))?
.with_second(
(fractional_part * Second.per(Hour) as f64 % Minute.per(Hour) as f64)
(fractional_part * Second::per(Hour) as f64 % Minute::per(Hour) as f64)
as _,
)
.ok_or(InvalidComponent("second"))?
.with_subsecond(
(fractional_part * Nanosecond.per(Hour) as f64
% Nanosecond.per(Second) as f64) as _,
(fractional_part * Nanosecond::per(Hour) as f64
% Nanosecond::per(Second) as f64) as _,
)
.ok_or(InvalidComponent("subsecond"))?;
return Ok(input);
@ -162,11 +166,11 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
*parsed = parsed
.with_minute(minute)
.ok_or(InvalidComponent("minute"))?
.with_second((fractional_part * Second.per(Minute) as f64) as _)
.with_second((fractional_part * Second::per(Minute) as f64) as _)
.ok_or(InvalidComponent("second"))?
.with_subsecond(
(fractional_part * Nanosecond.per(Minute) as f64
% Nanosecond.per(Second) as f64) as _,
(fractional_part * Nanosecond::per(Minute) as f64
% Nanosecond::per(Second) as f64) as _,
)
.ok_or(InvalidComponent("subsecond"))?;
return Ok(input);
@ -209,7 +213,7 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
Some(ParsedItem(input, (second, Some(fractional_part)))) => (
input,
second,
round(fractional_part * Nanosecond.per(Second) as f64) as _,
round(fractional_part * Nanosecond::per(Second) as f64) as _,
),
None if extended_kind.is_extended() => {
return Err(error::Parse::ParseFromDescription(InvalidComponent(
@ -254,9 +258,9 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
.and_then(|parsed_item| {
parsed_item.consume_value(|hour| {
parsed.set_offset_hour(if sign == b'-' {
-(hour as i8)
-hour.cast_signed()
} else {
hour as _
hour.cast_signed()
})
})
})
@ -276,9 +280,9 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
input = new_input;
parsed
.set_offset_minute_signed(if sign == b'-' {
-(min as i8)
-min.cast_signed()
} else {
min as _
min.cast_signed()
})
.ok_or(InvalidComponent("offset minute"))?;
}

View file

@ -31,6 +31,13 @@ impl<'a, T> ParsedItem<'a, T> {
f(self.1)?;
Some(self.0)
}
/// Filter the value with the provided function. If the function returns `false`, the value
/// is discarded and `None` is returned. Otherwise, the value is preserved and `Some(self)` is
/// returned.
pub(crate) fn filter(self, f: impl FnOnce(&T) -> bool) -> Option<ParsedItem<'a, T>> {
f(&self.1).then_some(self)
}
}
impl<'a> ParsedItem<'a, ()> {

View file

@ -2,21 +2,24 @@
use core::ops::Deref;
use crate::date_time::{maybe_offset_from_offset, MaybeOffset};
use num_conv::prelude::*;
use crate::error::TryFromParsed;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::FormatItem;
use crate::format_description::BorrowedFormatItem;
#[cfg(feature = "alloc")]
use crate::format_description::OwnedFormatItem;
use crate::internal_macros::bug;
use crate::parsing::{Parsed, ParsedItem};
use crate::{error, Date, DateTime, Month, Time, UtcOffset, Weekday};
use crate::{error, Date, Month, OffsetDateTime, Time, UtcOffset, Weekday};
/// A type that can be parsed.
#[cfg_attr(__time_03_docs, doc(notable_trait))]
#[doc(alias = "Parseable")]
pub trait Parsable: sealed::Sealed {}
impl Parsable for FormatItem<'_> {}
impl Parsable for [FormatItem<'_>] {}
impl Parsable for BorrowedFormatItem<'_> {}
impl Parsable for [BorrowedFormatItem<'_>] {}
#[cfg(feature = "alloc")]
impl Parsable for OwnedFormatItem {}
#[cfg(feature = "alloc")]
@ -31,6 +34,7 @@ impl<T: Deref> Parsable for T where T::Target: Parsable {}
mod sealed {
#[allow(clippy::wildcard_imports)]
use super::*;
use crate::PrimitiveDateTime;
/// Parse the item using a format description and an input.
pub trait Sealed {
@ -52,7 +56,9 @@ mod sealed {
if self.parse_into(input, &mut parsed)?.is_empty() {
Ok(parsed)
} else {
Err(error::Parse::UnexpectedTrailingCharacters)
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
))
}
}
@ -71,18 +77,23 @@ mod sealed {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`DateTime`] from the format description.
fn parse_date_time<O: MaybeOffset>(
/// Parse a [`PrimitiveDateTime`] from the format description.
fn parse_primitive_date_time(
&self,
input: &[u8],
) -> Result<DateTime<O>, error::Parse> {
) -> Result<PrimitiveDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`OffsetDateTime`] from the format description.
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
}
}
// region: custom formats
impl sealed::Sealed for FormatItem<'_> {
impl sealed::Sealed for BorrowedFormatItem<'_> {
fn parse_into<'a>(
&self,
input: &'a [u8],
@ -92,7 +103,7 @@ impl sealed::Sealed for FormatItem<'_> {
}
}
impl sealed::Sealed for [FormatItem<'_>] {
impl sealed::Sealed for [BorrowedFormatItem<'_>] {
fn parse_into<'a>(
&self,
input: &'a [u8],
@ -154,8 +165,8 @@ impl sealed::Sealed for Rfc2822 {
let colon = ascii_char::<b':'>;
let comma = ascii_char::<b','>;
let input = opt(fws)(input).into_inner();
let input = first_match(
let input = opt(cfws)(input).into_inner();
let weekday = first_match(
[
(b"Mon".as_slice(), Weekday::Monday),
(b"Tue".as_slice(), Weekday::Tuesday),
@ -166,11 +177,16 @@ impl sealed::Sealed for Rfc2822 {
(b"Sun".as_slice(), Weekday::Sunday),
],
false,
)(input)
.and_then(|item| item.consume_value(|value| parsed.set_weekday(value)))
.ok_or(InvalidComponent("weekday"))?;
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
)(input);
let input = if let Some(item) = weekday {
let input = item
.consume_value(|value| parsed.set_weekday(value))
.ok_or(InvalidComponent("weekday"))?;
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
opt(cfws)(input).into_inner()
} else {
input
};
let input = n_to_m_digits::<1, 2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_day(value)))
.ok_or(InvalidComponent("day"))?;
@ -199,7 +215,9 @@ impl sealed::Sealed for Rfc2822 {
Some(item) => {
let input = item
.flat_map(|year| if year >= 1900 { Some(year) } else { None })
.and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
.and_then(|item| {
item.consume_value(|value| parsed.set_year(value.cast_signed()))
})
.ok_or(InvalidComponent("year"))?;
fws(input).ok_or(InvalidLiteral)?.into_inner()
}
@ -207,7 +225,7 @@ impl sealed::Sealed for Rfc2822 {
let input = exactly_n_digits::<2, u32>(input)
.and_then(|item| {
item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
.map(|year| year as _)
.map(|year| year.cast_signed())
.consume_value(|value| parsed.set_year(value))
})
.ok_or(InvalidComponent("year"))?;
@ -237,7 +255,7 @@ impl sealed::Sealed for Rfc2822 {
};
// The RFC explicitly allows leap seconds.
parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true);
parsed.leap_second_allowed = true;
#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
let zone_literal = first_match(
@ -280,9 +298,9 @@ impl sealed::Sealed for Rfc2822 {
.and_then(|item| {
item.map(|offset_hour| {
if offset_sign == b'-' {
-(offset_hour as i8)
-offset_hour.cast_signed()
} else {
offset_hour as _
offset_hour.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_hour(value))
@ -290,14 +308,16 @@ impl sealed::Sealed for Rfc2822 {
.ok_or(InvalidComponent("offset hour"))?;
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
})
.ok_or(InvalidComponent("offset minute"))?;
let input = opt(cfws)(input).into_inner();
Ok(input)
}
fn parse_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, error::Parse> {
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
use crate::parsing::combinator::{
@ -307,10 +327,10 @@ impl sealed::Sealed for Rfc2822 {
let colon = ascii_char::<b':'>;
let comma = ascii_char::<b','>;
let input = opt(fws)(input).into_inner();
let input = opt(cfws)(input).into_inner();
// This parses the weekday, but we don't actually use the value anywhere. Because of this,
// just return `()` to avoid unnecessary generated code.
let ParsedItem(input, ()) = first_match(
let weekday = first_match(
[
(b"Mon".as_slice(), ()),
(b"Tue".as_slice(), ()),
@ -321,10 +341,14 @@ impl sealed::Sealed for Rfc2822 {
(b"Sun".as_slice(), ()),
],
false,
)(input)
.ok_or(InvalidComponent("weekday"))?;
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
)(input);
let input = if let Some(item) = weekday {
let input = item.into_inner();
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
opt(cfws)(input).into_inner()
} else {
input
};
let ParsedItem(input, day) =
n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
@ -417,26 +441,28 @@ impl sealed::Sealed for Rfc2822 {
.map(|item| {
item.map(|offset_hour| {
if offset_sign == b'-' {
-(offset_hour as i8)
-offset_hour.cast_signed()
} else {
offset_hour as _
offset_hour.cast_signed()
}
})
})
.ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_minute) =
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
(input, offset_hour, offset_minute as i8)
(input, offset_hour, offset_minute.cast_signed())
};
let input = opt(cfws)(input).into_inner();
if !input.is_empty() {
return Err(error::Parse::UnexpectedTrailingCharacters);
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
let mut nanosecond = 0;
let leap_second_input = if !O::HAS_LOGICAL_OFFSET {
false
} else if second == 60 {
let leap_second_input = if second == 60 {
second = 59;
nanosecond = 999_999_999;
true
@ -445,14 +471,10 @@ impl sealed::Sealed for Rfc2822 {
};
let dt = (|| {
let date = Date::from_calendar_date(year as _, month, day)?;
let date = Date::from_calendar_date(year.cast_signed(), month, day)?;
let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
Ok(DateTime {
date,
time,
offset: maybe_offset_from_offset::<O>(offset),
})
Ok(OffsetDateTime::new_in_offset(date, time, offset))
})()
.map_err(TryFromParsed::ComponentRange)?;
@ -487,7 +509,7 @@ impl sealed::Sealed for Rfc3339 {
let colon = ascii_char::<b':'>;
let input = exactly_n_digits::<4, u32>(input)
.and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
.and_then(|item| item.consume_value(|value| parsed.set_year(value.cast_signed())))
.ok_or(InvalidComponent("year"))?;
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, _>(input)
@ -515,11 +537,11 @@ impl sealed::Sealed for Rfc3339 {
let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) = any_digit(input)
.ok_or(InvalidComponent("subsecond"))?
.map(|v| (v - b'0') as u32 * 100_000_000);
.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0') as u32 * multiplier;
value += (digit - b'0').extend::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
@ -533,7 +555,7 @@ impl sealed::Sealed for Rfc3339 {
};
// The RFC explicitly allows leap seconds.
parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true);
parsed.leap_second_allowed = true;
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
parsed
@ -551,14 +573,15 @@ impl sealed::Sealed for Rfc3339 {
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.map(|offset_hour| {
if offset_sign == b'-' {
-(offset_hour as i8)
} else {
offset_hour as _
}
})
.consume_value(|value| parsed.set_offset_hour(value))
item.filter(|&offset_hour| offset_hour <= 23)?
.map(|offset_hour| {
if offset_sign == b'-' {
-offset_hour.cast_signed()
} else {
offset_hour.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_hour(value))
})
.ok_or(InvalidComponent("offset hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
@ -566,9 +589,9 @@ impl sealed::Sealed for Rfc3339 {
.and_then(|item| {
item.map(|offset_minute| {
if offset_sign == b'-' {
-(offset_minute as i8)
-offset_minute.cast_signed()
} else {
offset_minute as _
offset_minute.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_minute_signed(value))
@ -578,7 +601,7 @@ impl sealed::Sealed for Rfc3339 {
Ok(input)
}
fn parse_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, error::Parse> {
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::{
any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
@ -610,11 +633,11 @@ impl sealed::Sealed for Rfc3339 {
if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) = any_digit(input)
.ok_or(InvalidComponent("subsecond"))?
.map(|v| (v - b'0') as u32 * 100_000_000);
.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0') as u32 * multiplier;
value += (digit - b'0').extend::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
@ -629,21 +652,22 @@ impl sealed::Sealed for Rfc3339 {
} else {
let ParsedItem(input, offset_sign) =
sign(input).ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_hour) =
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
.and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
.ok_or(InvalidComponent("offset hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, offset_minute) =
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
UtcOffset::from_hms(
if offset_sign == b'-' {
-(offset_hour as i8)
-offset_hour.cast_signed()
} else {
offset_hour as _
offset_hour.cast_signed()
},
if offset_sign == b'-' {
-(offset_minute as i8)
-offset_minute.cast_signed()
} else {
offset_minute as _
offset_minute.cast_signed()
},
0,
)
@ -662,7 +686,9 @@ impl sealed::Sealed for Rfc3339 {
};
if !input.is_empty() {
return Err(error::Parse::UnexpectedTrailingCharacters);
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
// The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
@ -677,12 +703,11 @@ impl sealed::Sealed for Rfc3339 {
};
let date = Month::from_number(month)
.and_then(|month| Date::from_calendar_date(year as _, month, day))
.and_then(|month| Date::from_calendar_date(year.cast_signed(), month, day))
.map_err(TryFromParsed::ComponentRange)?;
let time = Time::from_hms_nano(hour, minute, second, nanosecond)
.map_err(TryFromParsed::ComponentRange)?;
let offset = maybe_offset_from_offset::<O>(offset);
let dt = DateTime { date, time, offset };
let dt = OffsetDateTime::new_in_offset(date, time, offset);
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
@ -714,6 +739,8 @@ impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
let mut offset_is_present = false;
let mut first_error = None;
parsed.leap_second_allowed = true;
match Self::parse_date(parsed, &mut extended_kind)(input) {
Ok(new_input) => {
input = new_input;

View file

@ -1,16 +1,22 @@
//! Information parsed from an input and format description.
use core::mem::MaybeUninit;
use core::num::{NonZeroU16, NonZeroU8};
use crate::date_time::{maybe_offset_from_offset, offset_kind, DateTime, MaybeOffset};
use deranged::{
OptionRangedI128, OptionRangedI32, OptionRangedI8, OptionRangedU16, OptionRangedU32,
OptionRangedU8, RangedI128, RangedI32, RangedI8, RangedU16, RangedU32, RangedU8,
};
use num_conv::prelude::*;
use crate::convert::{Day, Hour, Minute, Nanosecond, Second};
use crate::date::{MAX_YEAR, MIN_YEAR};
use crate::error::TryFromParsed::InsufficientInformation;
use crate::format_description::modifier::{WeekNumberRepr, YearRepr};
#[cfg(feature = "alloc")]
use crate::format_description::OwnedFormatItem;
use crate::format_description::{Component, FormatItem};
use crate::format_description::{modifier, BorrowedFormatItem, Component};
use crate::internal_macros::{bug, const_try_opt};
use crate::parsing::component::{
parse_day, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour,
parse_day, parse_end, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour,
parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second,
parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period,
};
@ -32,7 +38,7 @@ mod sealed {
}
}
impl sealed::AnyFormatItem for FormatItem<'_> {
impl sealed::AnyFormatItem for BorrowedFormatItem<'_> {
fn parse_item<'a>(
&self,
parsed: &mut Parsed,
@ -99,10 +105,6 @@ impl sealed::AnyFormatItem for OwnedFormatItem {
}
}
/// The type of the `flags` field in [`Parsed`]. Allows for changing a single location and having it
/// effect all uses.
type Flag = u32;
/// All information parsed.
///
/// This information is directly used to construct the final values.
@ -111,114 +113,111 @@ type Flag = u32;
/// control over values, in the instance that the default parser is insufficient.
#[derive(Debug, Clone, Copy)]
pub struct Parsed {
/// Bitflags indicating whether a particular field is present.
flags: Flag,
/// Calendar year.
year: MaybeUninit<i32>,
year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>,
/// The last two digits of the calendar year.
year_last_two: MaybeUninit<u8>,
year_last_two: OptionRangedU8<0, 99>,
/// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
iso_year: MaybeUninit<i32>,
iso_year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>,
/// The last two digits of the ISO week year.
iso_year_last_two: MaybeUninit<u8>,
iso_year_last_two: OptionRangedU8<0, 99>,
/// Month of the year.
month: Option<Month>,
/// Week of the year, where week one begins on the first Sunday of the calendar year.
sunday_week_number: MaybeUninit<u8>,
sunday_week_number: OptionRangedU8<0, 53>,
/// Week of the year, where week one begins on the first Monday of the calendar year.
monday_week_number: MaybeUninit<u8>,
monday_week_number: OptionRangedU8<0, 53>,
/// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
iso_week_number: Option<NonZeroU8>,
iso_week_number: OptionRangedU8<1, 53>,
/// Day of the week.
weekday: Option<Weekday>,
/// Day of the year.
ordinal: Option<NonZeroU16>,
ordinal: OptionRangedU16<1, 366>,
/// Day of the month.
day: Option<NonZeroU8>,
day: OptionRangedU8<1, 31>,
/// Hour within the day.
hour_24: MaybeUninit<u8>,
hour_24: OptionRangedU8<0, { Hour::per(Day) - 1 }>,
/// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
/// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
hour_12: Option<NonZeroU8>,
hour_12: OptionRangedU8<1, 12>,
/// Whether the `hour_12` field indicates a time that "PM".
hour_12_is_pm: Option<bool>,
/// Minute within the hour.
minute: MaybeUninit<u8>,
// minute: MaybeUninit<u8>,
minute: OptionRangedU8<0, { Minute::per(Hour) - 1 }>,
/// Second within the minute.
second: MaybeUninit<u8>,
// do not subtract one, as leap seconds may be allowed
second: OptionRangedU8<0, { Second::per(Minute) }>,
/// Nanosecond within the second.
subsecond: MaybeUninit<u32>,
subsecond: OptionRangedU32<0, { Nanosecond::per(Second) - 1 }>,
/// Whole hours of the UTC offset.
offset_hour: MaybeUninit<i8>,
offset_hour: OptionRangedI8<-23, 23>,
/// Minutes within the hour of the UTC offset.
offset_minute: MaybeUninit<i8>,
offset_minute:
OptionRangedI8<{ -((Minute::per(Hour) - 1) as i8) }, { (Minute::per(Hour) - 1) as _ }>,
/// Seconds within the minute of the UTC offset.
offset_second: MaybeUninit<i8>,
offset_second:
OptionRangedI8<{ -((Second::per(Minute) - 1) as i8) }, { (Second::per(Minute) - 1) as _ }>,
/// The Unix timestamp in nanoseconds.
unix_timestamp_nanos: MaybeUninit<i128>,
}
#[allow(clippy::missing_docs_in_private_items)]
impl Parsed {
const YEAR_FLAG: Flag = 1 << 0;
const YEAR_LAST_TWO_FLAG: Flag = 1 << 1;
const ISO_YEAR_FLAG: Flag = 1 << 2;
const ISO_YEAR_LAST_TWO_FLAG: Flag = 1 << 3;
const SUNDAY_WEEK_NUMBER_FLAG: Flag = 1 << 4;
const MONDAY_WEEK_NUMBER_FLAG: Flag = 1 << 5;
const HOUR_24_FLAG: Flag = 1 << 6;
const MINUTE_FLAG: Flag = 1 << 7;
const SECOND_FLAG: Flag = 1 << 8;
const SUBSECOND_FLAG: Flag = 1 << 9;
const OFFSET_HOUR_FLAG: Flag = 1 << 10;
const OFFSET_MINUTE_FLAG: Flag = 1 << 11;
const OFFSET_SECOND_FLAG: Flag = 1 << 12;
unix_timestamp_nanos: OptionRangedI128<
{
OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC)
.unix_timestamp_nanos()
},
{
OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::UTC)
.unix_timestamp_nanos()
},
>,
/// Indicates whether the [`UtcOffset`] is negative. This information is obtained when parsing
/// the offset hour, but may not otherwise be stored due to "-0" being equivalent to "0".
offset_is_negative: Option<bool>,
/// Indicates whether a leap second is permitted to be parsed. This is required by some
/// well-known formats.
pub(super) const LEAP_SECOND_ALLOWED_FLAG: Flag = 1 << 13;
/// Indicates whether the `UtcOffset` is negative. This information is obtained when parsing the
/// offset hour, but may not otherwise be stored due to "-0" being equivalent to "0".
const OFFSET_IS_NEGATIVE_FLAG: Flag = 1 << 14;
/// Does the value at `OFFSET_IS_NEGATIVE_FLAG` have any semantic meaning, or is it just the
/// default value? If the latter, the value should be considered to have no meaning.
const OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED: Flag = 1 << 15;
const UNIX_TIMESTAMP_NANOS_FLAG: Flag = 1 << 16;
pub(super) leap_second_allowed: bool,
}
impl Default for Parsed {
fn default() -> Self {
Self::new()
}
}
impl Parsed {
/// Create a new instance of `Parsed` with no information known.
pub const fn new() -> Self {
Self {
flags: 0,
year: MaybeUninit::uninit(),
year_last_two: MaybeUninit::uninit(),
iso_year: MaybeUninit::uninit(),
iso_year_last_two: MaybeUninit::uninit(),
year: OptionRangedI32::None,
year_last_two: OptionRangedU8::None,
iso_year: OptionRangedI32::None,
iso_year_last_two: OptionRangedU8::None,
month: None,
sunday_week_number: MaybeUninit::uninit(),
monday_week_number: MaybeUninit::uninit(),
iso_week_number: None,
sunday_week_number: OptionRangedU8::None,
monday_week_number: OptionRangedU8::None,
iso_week_number: OptionRangedU8::None,
weekday: None,
ordinal: None,
day: None,
hour_24: MaybeUninit::uninit(),
hour_12: None,
ordinal: OptionRangedU16::None,
day: OptionRangedU8::None,
hour_24: OptionRangedU8::None,
hour_12: OptionRangedU8::None,
hour_12_is_pm: None,
minute: MaybeUninit::uninit(),
second: MaybeUninit::uninit(),
subsecond: MaybeUninit::uninit(),
offset_hour: MaybeUninit::uninit(),
offset_minute: MaybeUninit::uninit(),
offset_second: MaybeUninit::uninit(),
unix_timestamp_nanos: MaybeUninit::uninit(),
minute: OptionRangedU8::None,
second: OptionRangedU8::None,
subsecond: OptionRangedU32::None,
offset_hour: OptionRangedI8::None,
offset_minute: OptionRangedI8::None,
offset_second: OptionRangedI8::None,
unix_timestamp_nanos: OptionRangedI128::None,
offset_is_negative: None,
leap_second_allowed: false,
}
}
/// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining
/// input is returned as the `Ok` value.
/// Parse a single [`BorrowedFormatItem`] or [`OwnedFormatItem`], mutating the struct. The
/// remaining input is returned as the `Ok` value.
///
/// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not
/// fail; the input will be returned as-is if the expected format is not present.
/// If a [`BorrowedFormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing
/// will not fail; the input will be returned as-is if the expected format is not present.
pub fn parse_item<'a>(
&mut self,
input: &'a [u8],
@ -227,11 +226,11 @@ impl Parsed {
item.parse_item(self, input)
}
/// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The
/// remaining input is returned as the `Ok` value.
/// Parse a sequence of [`BorrowedFormatItem`]s or [`OwnedFormatItem`]s, mutating the struct.
/// The remaining input is returned as the `Ok` value.
///
/// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail
/// to parse. `self` will not be mutated in this instance.
/// This method will fail if any of the contained [`BorrowedFormatItem`]s or
/// [`OwnedFormatItem`]s fail to parse. `self` will not be mutated in this instance.
pub fn parse_items<'a>(
&mut self,
mut input: &'a [u8],
@ -283,11 +282,11 @@ impl Parsed {
let ParsedItem(remaining, value) =
parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?;
match modifiers.repr {
WeekNumberRepr::Iso => {
modifier::WeekNumberRepr::Iso => {
NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value))
}
WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
WeekNumberRepr::Monday => self.set_monday_week_number(value),
modifier::WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
modifier::WeekNumberRepr::Monday => self.set_monday_week_number(value),
}
.ok_or(InvalidComponent("week number"))?;
Ok(remaining)
@ -296,10 +295,14 @@ impl Parsed {
let ParsedItem(remaining, value) =
parse_year(input, modifiers).ok_or(InvalidComponent("year"))?;
match (modifiers.iso_week_based, modifiers.repr) {
(false, YearRepr::Full) => self.set_year(value),
(false, YearRepr::LastTwo) => self.set_year_last_two(value as _),
(true, YearRepr::Full) => self.set_iso_year(value),
(true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _),
(false, modifier::YearRepr::Full) => self.set_year(value),
(false, modifier::YearRepr::LastTwo) => {
self.set_year_last_two(value.cast_unsigned().truncate())
}
(true, modifier::YearRepr::Full) => self.set_iso_year(value),
(true, modifier::YearRepr::LastTwo) => {
self.set_iso_year_last_two(value.cast_unsigned().truncate())
}
}
.ok_or(InvalidComponent("year"))?;
Ok(remaining)
@ -332,9 +335,9 @@ impl Parsed {
Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers)
.and_then(|parsed| {
parsed.consume_value(|(value, is_negative)| {
self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED, true);
self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG, is_negative);
self.set_offset_hour(value)
self.set_offset_hour(value)?;
self.offset_is_negative = Some(is_negative);
Some(())
})
})
.ok_or(InvalidComponent("offset hour")),
@ -356,145 +359,152 @@ impl Parsed {
parsed.consume_value(|value| self.set_unix_timestamp_nanos(value))
})
.ok_or(InvalidComponent("unix_timestamp")),
Component::End(modifiers) => parse_end(input, modifiers)
.map(ParsedItem::<()>::into_inner)
.ok_or(error::ParseFromDescription::UnexpectedTrailingCharacters),
}
}
/// Get the value of the provided flag.
const fn get_flag(&self, flag: Flag) -> bool {
self.flags & flag == flag
}
/// Set the value of the provided flag.
pub(super) fn set_flag(&mut self, flag: Flag, value: bool) {
if value {
self.flags |= flag;
} else {
self.flags &= !flag;
}
}
}
/// Generate getters for each of the fields.
macro_rules! getters {
($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
getters!(! $(@$flag)? $name: $ty);
)*};
(! $name:ident : $ty:ty) => {
/// Obtain the named component.
pub const fn $name(&self) -> Option<$ty> {
self.$name
}
};
(! @$flag:ident $name:ident : $ty:ty) => {
/// Obtain the named component.
pub const fn $name(&self) -> Option<$ty> {
if !self.get_flag(Self::$flag) {
None
} else {
// SAFETY: We just checked if the field is present.
Some(unsafe { self.$name.assume_init() })
}
}
};
}
/// Getter methods
impl Parsed {
getters! {
@YEAR_FLAG year: i32,
@YEAR_LAST_TWO_FLAG year_last_two: u8,
@ISO_YEAR_FLAG iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
month: Month,
@SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
iso_week_number: NonZeroU8,
weekday: Weekday,
ordinal: NonZeroU16,
day: NonZeroU8,
@HOUR_24_FLAG hour_24: u8,
hour_12: NonZeroU8,
hour_12_is_pm: bool,
@MINUTE_FLAG minute: u8,
@SECOND_FLAG second: u8,
@SUBSECOND_FLAG subsecond: u32,
@OFFSET_HOUR_FLAG offset_hour: i8,
@UNIX_TIMESTAMP_NANOS_FLAG unix_timestamp_nanos: i128,
/// Obtain the `year` component.
pub const fn year(&self) -> Option<i32> {
self.year.get_primitive()
}
/// Obtain the absolute value of the offset minute.
/// Obtain the `year_last_two` component.
pub const fn year_last_two(&self) -> Option<u8> {
self.year_last_two.get_primitive()
}
/// Obtain the `iso_year` component.
pub const fn iso_year(&self) -> Option<i32> {
self.iso_year.get_primitive()
}
/// Obtain the `iso_year_last_two` component.
pub const fn iso_year_last_two(&self) -> Option<u8> {
self.iso_year_last_two.get_primitive()
}
/// Obtain the `month` component.
pub const fn month(&self) -> Option<Month> {
self.month
}
/// Obtain the `sunday_week_number` component.
pub const fn sunday_week_number(&self) -> Option<u8> {
self.sunday_week_number.get_primitive()
}
/// Obtain the `monday_week_number` component.
pub const fn monday_week_number(&self) -> Option<u8> {
self.monday_week_number.get_primitive()
}
/// Obtain the `iso_week_number` component.
pub const fn iso_week_number(&self) -> Option<NonZeroU8> {
NonZeroU8::new(const_try_opt!(self.iso_week_number.get_primitive()))
}
/// Obtain the `weekday` component.
pub const fn weekday(&self) -> Option<Weekday> {
self.weekday
}
/// Obtain the `ordinal` component.
pub const fn ordinal(&self) -> Option<NonZeroU16> {
NonZeroU16::new(const_try_opt!(self.ordinal.get_primitive()))
}
/// Obtain the `day` component.
pub const fn day(&self) -> Option<NonZeroU8> {
NonZeroU8::new(const_try_opt!(self.day.get_primitive()))
}
/// Obtain the `hour_24` component.
pub const fn hour_24(&self) -> Option<u8> {
self.hour_24.get_primitive()
}
/// Obtain the `hour_12` component.
pub const fn hour_12(&self) -> Option<NonZeroU8> {
NonZeroU8::new(const_try_opt!(self.hour_12.get_primitive()))
}
/// Obtain the `hour_12_is_pm` component.
pub const fn hour_12_is_pm(&self) -> Option<bool> {
self.hour_12_is_pm
}
/// Obtain the `minute` component.
pub const fn minute(&self) -> Option<u8> {
self.minute.get_primitive()
}
/// Obtain the `second` component.
pub const fn second(&self) -> Option<u8> {
self.second.get_primitive()
}
/// Obtain the `subsecond` component.
pub const fn subsecond(&self) -> Option<u32> {
self.subsecond.get_primitive()
}
/// Obtain the `offset_hour` component.
pub const fn offset_hour(&self) -> Option<i8> {
self.offset_hour.get_primitive()
}
/// Obtain the absolute value of the `offset_minute` component.
#[doc(hidden)]
#[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")]
pub const fn offset_minute(&self) -> Option<u8> {
Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs())
}
/// Obtain the offset minute as an `i8`.
/// Obtain the `offset_minute` component.
pub const fn offset_minute_signed(&self) -> Option<i8> {
if !self.get_flag(Self::OFFSET_MINUTE_FLAG) {
None
} else {
// SAFETY: We just checked if the field is present.
let value = unsafe { self.offset_minute.assume_init() };
if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED)
&& (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG))
{
Some(-value)
} else {
Some(value)
}
match (self.offset_minute.get_primitive(), self.offset_is_negative) {
(Some(offset_minute), Some(true)) => Some(-offset_minute),
(Some(offset_minute), _) => Some(offset_minute),
(None, _) => None,
}
}
/// Obtain the absolute value of the offset second.
/// Obtain the absolute value of the `offset_second` component.
#[doc(hidden)]
#[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")]
pub const fn offset_second(&self) -> Option<u8> {
Some(const_try_opt!(self.offset_second_signed()).unsigned_abs())
}
/// Obtain the offset second as an `i8`.
/// Obtain the `offset_second` component.
pub const fn offset_second_signed(&self) -> Option<i8> {
if !self.get_flag(Self::OFFSET_SECOND_FLAG) {
None
} else {
// SAFETY: We just checked if the field is present.
let value = unsafe { self.offset_second.assume_init() };
if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED)
&& (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG))
{
Some(-value)
} else {
Some(value)
}
match (self.offset_second.get_primitive(), self.offset_is_negative) {
(Some(offset_second), Some(true)) => Some(-offset_second),
(Some(offset_second), _) => Some(offset_second),
(None, _) => None,
}
}
/// Obtain the `unix_timestamp_nanos` component.
pub const fn unix_timestamp_nanos(&self) -> Option<i128> {
self.unix_timestamp_nanos.get_primitive()
}
}
/// Generate setters for each of the fields.
///
/// This macro should only be used for fields where the value is not validated beyond its type.
/// Generate setters based on the builders.
macro_rules! setters {
($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
setters!(! $(@$flag)? $setter_name $name: $ty);
($($name:ident $setter:ident $builder:ident $type:ty;)*) => {$(
#[doc = concat!("Set the `", stringify!($setter), "` component.")]
pub fn $setter(&mut self, value: $type) -> Option<()> {
*self = self.$builder(value)?;
Some(())
}
)*};
(! $setter_name:ident $name:ident : $ty:ty) => {
/// Set the named component.
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
self.$name = Some(value);
Some(())
}
};
(! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => {
/// Set the named component.
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
self.$name = MaybeUninit::new(value);
self.set_flag(Self::$flag, true);
Some(())
}
};
}
/// Setter methods
@ -503,92 +513,56 @@ macro_rules! setters {
/// setters _may_ fail if the value is invalid, though behavior is not guaranteed.
impl Parsed {
setters! {
@YEAR_FLAG set_year year: i32,
@YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8,
@ISO_YEAR_FLAG set_iso_year iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8,
set_month month: Month,
@SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8,
set_iso_week_number iso_week_number: NonZeroU8,
set_weekday weekday: Weekday,
set_ordinal ordinal: NonZeroU16,
set_day day: NonZeroU8,
@HOUR_24_FLAG set_hour_24 hour_24: u8,
set_hour_12 hour_12: NonZeroU8,
set_hour_12_is_pm hour_12_is_pm: bool,
@MINUTE_FLAG set_minute minute: u8,
@SECOND_FLAG set_second second: u8,
@SUBSECOND_FLAG set_subsecond subsecond: u32,
@OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8,
@UNIX_TIMESTAMP_NANOS_FLAG set_unix_timestamp_nanos unix_timestamp_nanos: i128,
year set_year with_year i32;
year_last_two set_year_last_two with_year_last_two u8;
iso_year set_iso_year with_iso_year i32;
iso_year_last_two set_iso_year_last_two with_iso_year_last_two u8;
month set_month with_month Month;
sunday_week_number set_sunday_week_number with_sunday_week_number u8;
monday_week_number set_monday_week_number with_monday_week_number u8;
iso_week_number set_iso_week_number with_iso_week_number NonZeroU8;
weekday set_weekday with_weekday Weekday;
ordinal set_ordinal with_ordinal NonZeroU16;
day set_day with_day NonZeroU8;
hour_24 set_hour_24 with_hour_24 u8;
hour_12 set_hour_12 with_hour_12 NonZeroU8;
hour_12_is_pm set_hour_12_is_pm with_hour_12_is_pm bool;
minute set_minute with_minute u8;
second set_second with_second u8;
subsecond set_subsecond with_subsecond u32;
offset_hour set_offset_hour with_offset_hour i8;
offset_minute set_offset_minute_signed with_offset_minute_signed i8;
offset_second set_offset_second_signed with_offset_second_signed i8;
unix_timestamp_nanos set_unix_timestamp_nanos with_unix_timestamp_nanos i128;
}
/// Set the named component.
/// Set the `offset_minute` component.
#[doc(hidden)]
#[deprecated(
since = "0.3.8",
note = "use `parsed.set_offset_minute_signed()` instead"
)]
pub fn set_offset_minute(&mut self, value: u8) -> Option<()> {
if value > i8::MAX as u8 {
if value > i8::MAX.cast_unsigned() {
None
} else {
self.set_offset_minute_signed(value as _)
self.set_offset_minute_signed(value.cast_signed())
}
}
/// Set the `offset_minute` component.
pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> {
self.offset_minute = MaybeUninit::new(value);
self.set_flag(Self::OFFSET_MINUTE_FLAG, true);
Some(())
}
/// Set the named component.
#[doc(hidden)]
#[deprecated(
since = "0.3.8",
note = "use `parsed.set_offset_second_signed()` instead"
)]
pub fn set_offset_second(&mut self, value: u8) -> Option<()> {
if value > i8::MAX as u8 {
if value > i8::MAX.cast_unsigned() {
None
} else {
self.set_offset_second_signed(value as _)
self.set_offset_second_signed(value.cast_signed())
}
}
/// Set the `offset_second` component.
pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> {
self.offset_second = MaybeUninit::new(value);
self.set_flag(Self::OFFSET_SECOND_FLAG, true);
Some(())
}
}
/// Generate build methods for each of the fields.
///
/// This macro should only be used for fields where the value is not validated beyond its type.
macro_rules! builders {
($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
builders!(! $(@$flag)? $builder_name $name: $ty);
)*};
(! $builder_name:ident $name:ident : $ty:ty) => {
/// Set the named component and return `self`.
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
self.$name = Some(value);
Some(self)
}
};
(! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
/// Set the named component and return `self`.
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
self.$name = MaybeUninit::new(value);
self.flags |= Self::$flag;
Some(self)
}
};
}
/// Builder methods
@ -596,29 +570,115 @@ macro_rules! builders {
/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if
/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
impl Parsed {
builders! {
@YEAR_FLAG with_year year: i32,
@YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
@ISO_YEAR_FLAG with_iso_year iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
with_month month: Month,
@SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
with_iso_week_number iso_week_number: NonZeroU8,
with_weekday weekday: Weekday,
with_ordinal ordinal: NonZeroU16,
with_day day: NonZeroU8,
@HOUR_24_FLAG with_hour_24 hour_24: u8,
with_hour_12 hour_12: NonZeroU8,
with_hour_12_is_pm hour_12_is_pm: bool,
@MINUTE_FLAG with_minute minute: u8,
@SECOND_FLAG with_second second: u8,
@SUBSECOND_FLAG with_subsecond subsecond: u32,
@OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
@UNIX_TIMESTAMP_NANOS_FLAG with_unix_timestamp_nanos unix_timestamp_nanos: i128,
/// Set the `year` component and return `self`.
pub const fn with_year(mut self, value: i32) -> Option<Self> {
self.year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value)));
Some(self)
}
/// Set the named component and return `self`.
/// Set the `year_last_two` component and return `self`.
pub const fn with_year_last_two(mut self, value: u8) -> Option<Self> {
self.year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `iso_year` component and return `self`.
pub const fn with_iso_year(mut self, value: i32) -> Option<Self> {
self.iso_year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value)));
Some(self)
}
/// Set the `iso_year_last_two` component and return `self`.
pub const fn with_iso_year_last_two(mut self, value: u8) -> Option<Self> {
self.iso_year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `month` component and return `self`.
pub const fn with_month(mut self, value: Month) -> Option<Self> {
self.month = Some(value);
Some(self)
}
/// Set the `sunday_week_number` component and return `self`.
pub const fn with_sunday_week_number(mut self, value: u8) -> Option<Self> {
self.sunday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `monday_week_number` component and return `self`.
pub const fn with_monday_week_number(mut self, value: u8) -> Option<Self> {
self.monday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `iso_week_number` component and return `self`.
pub const fn with_iso_week_number(mut self, value: NonZeroU8) -> Option<Self> {
self.iso_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
Some(self)
}
/// Set the `weekday` component and return `self`.
pub const fn with_weekday(mut self, value: Weekday) -> Option<Self> {
self.weekday = Some(value);
Some(self)
}
/// Set the `ordinal` component and return `self`.
pub const fn with_ordinal(mut self, value: NonZeroU16) -> Option<Self> {
self.ordinal = OptionRangedU16::Some(const_try_opt!(RangedU16::new(value.get())));
Some(self)
}
/// Set the `day` component and return `self`.
pub const fn with_day(mut self, value: NonZeroU8) -> Option<Self> {
self.day = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
Some(self)
}
/// Set the `hour_24` component and return `self`.
pub const fn with_hour_24(mut self, value: u8) -> Option<Self> {
self.hour_24 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `hour_12` component and return `self`.
pub const fn with_hour_12(mut self, value: NonZeroU8) -> Option<Self> {
self.hour_12 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
Some(self)
}
/// Set the `hour_12_is_pm` component and return `self`.
pub const fn with_hour_12_is_pm(mut self, value: bool) -> Option<Self> {
self.hour_12_is_pm = Some(value);
Some(self)
}
/// Set the `minute` component and return `self`.
pub const fn with_minute(mut self, value: u8) -> Option<Self> {
self.minute = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `second` component and return `self`.
pub const fn with_second(mut self, value: u8) -> Option<Self> {
self.second = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
Some(self)
}
/// Set the `subsecond` component and return `self`.
pub const fn with_subsecond(mut self, value: u32) -> Option<Self> {
self.subsecond = OptionRangedU32::Some(const_try_opt!(RangedU32::new(value)));
Some(self)
}
/// Set the `offset_hour` component and return `self`.
pub const fn with_offset_hour(mut self, value: i8) -> Option<Self> {
self.offset_hour = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
Some(self)
}
/// Set the `offset_minute` component and return `self`.
#[doc(hidden)]
#[deprecated(
since = "0.3.8",
@ -634,12 +694,11 @@ impl Parsed {
/// Set the `offset_minute` component and return `self`.
pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
self.offset_minute = MaybeUninit::new(value);
self.flags |= Self::OFFSET_MINUTE_FLAG;
self.offset_minute = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
Some(self)
}
/// Set the named component and return `self`.
/// Set the `offset_minute` component and return `self`.
#[doc(hidden)]
#[deprecated(
since = "0.3.8",
@ -655,8 +714,13 @@ impl Parsed {
/// Set the `offset_second` component and return `self`.
pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
self.offset_second = MaybeUninit::new(value);
self.flags |= Self::OFFSET_SECOND_FLAG;
self.offset_second = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
Some(self)
}
/// Set the `unix_timestamp_nanos` component and return `self`.
pub const fn with_unix_timestamp_nanos(mut self, value: i128) -> Option<Self> {
self.unix_timestamp_nanos = OptionRangedI128::Some(const_try_opt!(RangedI128::new(value)));
Some(self)
}
}
@ -682,7 +746,8 @@ impl TryFrom<Parsed> for Date {
/// Get the value needed to adjust the ordinal day for Sunday and Monday-based week
/// numbering.
const fn adjustment(year: i32) -> i16 {
match Date::__from_ordinal_date_unchecked(year, 1).weekday() {
// Safety: `ordinal` is not zero.
match unsafe { Date::__from_ordinal_date_unchecked(year, 1) }.weekday() {
Weekday::Monday => 7,
Weekday::Tuesday => 1,
Weekday::Wednesday => 2,
@ -706,15 +771,17 @@ impl TryFrom<Parsed> for Date {
)?),
(year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date(
year,
(sunday_week_number as i16 * 7 + weekday.number_days_from_sunday() as i16
(sunday_week_number.cast_signed().extend::<i16>() * 7
+ weekday.number_days_from_sunday().cast_signed().extend::<i16>()
- adjustment(year)
+ 1) as u16,
+ 1).cast_unsigned(),
)?),
(year, monday_week_number, weekday) => Ok(Self::from_ordinal_date(
year,
(monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16
(monday_week_number.cast_signed().extend::<i16>() * 7
+ weekday.number_days_from_monday().cast_signed().extend::<i16>()
- adjustment(year)
+ 1) as u16,
+ 1).cast_unsigned(),
)?),
_ => Err(InsufficientInformation),
}
@ -733,6 +800,7 @@ impl TryFrom<Parsed> for Time {
(_, Some(hour), Some(true)) => hour.get() + 12,
_ => return Err(InsufficientInformation),
};
if parsed.hour_24().is_none()
&& parsed.hour_12().is_some()
&& parsed.hour_12_is_pm().is_some()
@ -742,10 +810,17 @@ impl TryFrom<Parsed> for Time {
{
return Ok(Self::from_hms_nano(hour, 0, 0, 0)?);
}
let minute = parsed.minute().ok_or(InsufficientInformation)?;
let second = parsed.second().unwrap_or(0);
let subsecond = parsed.subsecond().unwrap_or(0);
Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
// Reject combinations such as hour-second with minute omitted.
match (parsed.minute(), parsed.second(), parsed.subsecond()) {
(None, None, None) => Ok(Self::from_hms_nano(hour, 0, 0, 0)?),
(Some(minute), None, None) => Ok(Self::from_hms_nano(hour, minute, 0, 0)?),
(Some(minute), Some(second), None) => Ok(Self::from_hms_nano(hour, minute, second, 0)?),
(Some(minute), Some(second), Some(subsecond)) => {
Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
}
_ => Err(InsufficientInformation),
}
}
}
@ -772,58 +847,46 @@ impl TryFrom<Parsed> for UtcOffset {
}
impl TryFrom<Parsed> for PrimitiveDateTime {
type Error = <DateTime<offset_kind::None> as TryFrom<Parsed>>::Error;
type Error = error::TryFromParsed;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
parsed.try_into().map(Self)
Ok(Self::new(parsed.try_into()?, parsed.try_into()?))
}
}
impl TryFrom<Parsed> for OffsetDateTime {
type Error = <DateTime<offset_kind::Fixed> as TryFrom<Parsed>>::Error;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
parsed.try_into().map(Self)
}
}
impl<O: MaybeOffset> TryFrom<Parsed> for DateTime<O> {
type Error = error::TryFromParsed;
#[allow(clippy::unwrap_in_result)] // We know the values are valid.
fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
if O::HAS_LOGICAL_OFFSET {
if let Some(timestamp) = parsed.unix_timestamp_nanos() {
let DateTime { date, time, offset } =
DateTime::<offset_kind::Fixed>::from_unix_timestamp_nanos(timestamp)?;
return Ok(Self {
date,
time,
offset: maybe_offset_from_offset::<O>(offset),
});
if let Some(timestamp) = parsed.unix_timestamp_nanos() {
let mut value = Self::from_unix_timestamp_nanos(timestamp)?;
if let Some(subsecond) = parsed.subsecond() {
value = value.replace_nanosecond(subsecond)?;
}
return Ok(value);
}
// Some well-known formats explicitly allow leap seconds. We don't currently support them,
// so treat it as the nearest preceding moment that can be represented. Because leap seconds
// always fall at the end of a month UTC, reject any that are at other times.
let leap_second_input =
if parsed.get_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG) && parsed.second() == Some(60) {
parsed.set_second(59).expect("59 is a valid second");
parsed
.set_subsecond(999_999_999)
.expect("999_999_999 is a valid subsecond");
true
} else {
false
};
let dt = Self {
date: Date::try_from(parsed)?,
time: Time::try_from(parsed)?,
offset: O::try_from_parsed(parsed)?,
let leap_second_input = if parsed.leap_second_allowed && parsed.second() == Some(60) {
if parsed.set_second(59).is_none() {
bug!("59 is a valid second");
}
if parsed.set_subsecond(999_999_999).is_none() {
bug!("999_999_999 is a valid subsecond");
}
true
} else {
false
};
let dt = Self::new_in_offset(
Date::try_from(parsed)?,
Time::try_from(parsed)?,
UtcOffset::try_from(parsed)?,
);
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::TryFromParsed::ComponentRange(
error::ComponentRange {

View file

@ -1,24 +1,29 @@
//! The [`PrimitiveDateTime`] struct and its associated `impl`s.
#[cfg(feature = "formatting")]
use alloc::string::String;
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration as StdDuration;
#[cfg(feature = "formatting")]
use std::io;
use crate::date_time::offset_kind;
use powerfmt::ext::FormatterExt as _;
use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};
#[cfg(feature = "formatting")]
use crate::formatting::Formattable;
use crate::internal_macros::{const_try, const_try_opt};
#[cfg(feature = "parsing")]
use crate::parsing::Parsable;
use crate::{error, Date, DateTime, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
/// The actual type doing all the work.
type Inner = DateTime<offset_kind::None>;
use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
/// Combined date and time.
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PrimitiveDateTime(#[allow(clippy::missing_docs_in_private_items)] pub(crate) Inner);
pub struct PrimitiveDateTime {
date: Date,
time: Time,
}
impl PrimitiveDateTime {
/// The smallest value that can be represented by `PrimitiveDateTime`.
@ -48,7 +53,10 @@ impl PrimitiveDateTime {
doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));"
)]
/// ```
pub const MIN: Self = Self(Inner::MIN);
pub const MIN: Self = Self {
date: Date::MIN,
time: Time::MIDNIGHT,
};
/// The largest value that can be represented by `PrimitiveDateTime`.
///
@ -77,7 +85,10 @@ impl PrimitiveDateTime {
doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));"
)]
/// ```
pub const MAX: Self = Self(Inner::MAX);
pub const MAX: Self = Self {
date: Date::MAX,
time: Time::MAX,
};
/// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`].
///
@ -90,7 +101,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn new(date: Date, time: Time) -> Self {
Self(Inner::new(date, time))
Self { date, time }
}
// region: component getters
@ -101,7 +112,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01));
/// ```
pub const fn date(self) -> Date {
self.0.date()
self.date
}
/// Get the [`Time`] component of the `PrimitiveDateTime`.
@ -109,8 +120,9 @@ impl PrimitiveDateTime {
/// ```rust
/// # use time_macros::{datetime, time};
/// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00));
/// ```
pub const fn time(self) -> Time {
self.0.time()
self.time
}
// endregion component getters
@ -124,7 +136,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020);
/// ```
pub const fn year(self) -> i32 {
self.0.year()
self.date().year()
}
/// Get the month of the date.
@ -136,7 +148,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December);
/// ```
pub const fn month(self) -> Month {
self.0.month()
self.date().month()
}
/// Get the day of the date.
@ -149,7 +161,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-12-31 0:00).day(), 31);
/// ```
pub const fn day(self) -> u8 {
self.0.day()
self.date().day()
}
/// Get the day of the year.
@ -162,7 +174,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365);
/// ```
pub const fn ordinal(self) -> u16 {
self.0.ordinal()
self.date().ordinal()
}
/// Get the ISO week number.
@ -178,7 +190,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53);
/// ```
pub const fn iso_week(self) -> u8 {
self.0.iso_week()
self.date().iso_week()
}
/// Get the week number where week 1 begins on the first Sunday.
@ -193,7 +205,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2021-01-01 0:00).sunday_based_week(), 0);
/// ```
pub const fn sunday_based_week(self) -> u8 {
self.0.sunday_based_week()
self.date().sunday_based_week()
}
/// Get the week number where week 1 begins on the first Monday.
@ -208,7 +220,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2021-01-01 0:00).monday_based_week(), 0);
/// ```
pub const fn monday_based_week(self) -> u8 {
self.0.monday_based_week()
self.date().monday_based_week()
}
/// Get the year, month, and day.
@ -222,7 +234,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn to_calendar_date(self) -> (i32, Month, u8) {
self.0.to_calendar_date()
self.date().to_calendar_date()
}
/// Get the year and ordinal day number.
@ -232,7 +244,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1));
/// ```
pub const fn to_ordinal_date(self) -> (i32, u16) {
self.0.to_ordinal_date()
self.date().to_ordinal_date()
}
/// Get the ISO 8601 year, week number, and weekday.
@ -262,7 +274,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
self.0.to_iso_week_date()
self.date().to_iso_week_date()
}
/// Get the weekday.
@ -284,7 +296,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday);
/// ```
pub const fn weekday(self) -> Weekday {
self.0.weekday()
self.date().weekday()
}
/// Get the Julian day for the date. The time is not taken into account for this calculation.
@ -300,7 +312,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-12-31 0:00).to_julian_day(), 2_458_849);
/// ```
pub const fn to_julian_day(self) -> i32 {
self.0.to_julian_day()
self.date().to_julian_day()
}
// endregion date getters
@ -313,7 +325,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2020-01-01 23:59:59).as_hms(), (23, 59, 59));
/// ```
pub const fn as_hms(self) -> (u8, u8, u8) {
self.0.as_hms()
self.time().as_hms()
}
/// Get the clock hour, minute, second, and millisecond.
@ -327,7 +339,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
self.0.as_hms_milli()
self.time().as_hms_milli()
}
/// Get the clock hour, minute, second, and microsecond.
@ -341,7 +353,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
self.0.as_hms_micro()
self.time().as_hms_micro()
}
/// Get the clock hour, minute, second, and nanosecond.
@ -355,7 +367,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
self.0.as_hms_nano()
self.time().as_hms_nano()
}
/// Get the clock hour.
@ -368,7 +380,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23);
/// ```
pub const fn hour(self) -> u8 {
self.0.hour()
self.time().hour()
}
/// Get the minute within the hour.
@ -381,7 +393,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59);
/// ```
pub const fn minute(self) -> u8 {
self.0.minute()
self.time().minute()
}
/// Get the second within the minute.
@ -394,7 +406,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59);
/// ```
pub const fn second(self) -> u8 {
self.0.second()
self.time().second()
}
/// Get the milliseconds within the second.
@ -407,7 +419,7 @@ impl PrimitiveDateTime {
/// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999);
/// ```
pub const fn millisecond(self) -> u16 {
self.0.millisecond()
self.time().millisecond()
}
/// Get the microseconds within the second.
@ -423,7 +435,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn microsecond(self) -> u32 {
self.0.microsecond()
self.time().microsecond()
}
/// Get the nanoseconds within the second.
@ -439,7 +451,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn nanosecond(self) -> u32 {
self.0.nanosecond()
self.time().nanosecond()
}
// endregion time getters
@ -463,7 +475,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime {
OffsetDateTime(self.0.assume_offset(offset))
OffsetDateTime::new_in_offset(self.date, self.time, offset)
}
/// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an
@ -477,7 +489,7 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn assume_utc(self) -> OffsetDateTime {
OffsetDateTime(self.0.assume_utc())
self.assume_offset(UtcOffset::UTC)
}
// endregion attach offset
@ -499,7 +511,17 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn checked_add(self, duration: Duration) -> Option<Self> {
Some(Self(const_try_opt!(self.0.checked_add(duration))))
let (date_adjustment, time) = self.time.adjusting_add(duration);
let date = const_try_opt!(self.date.checked_add(duration));
Some(Self {
date: match date_adjustment {
util::DateAdjustment::Previous => const_try_opt!(date.previous_day()),
util::DateAdjustment::Next => const_try_opt!(date.next_day()),
util::DateAdjustment::None => date,
},
time,
})
}
/// Computes `self - duration`, returning `None` if an overflow occurred.
@ -519,7 +541,17 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
Some(Self(const_try_opt!(self.0.checked_sub(duration))))
let (date_adjustment, time) = self.time.adjusting_sub(duration);
let date = const_try_opt!(self.date.checked_sub(duration));
Some(Self {
date: match date_adjustment {
util::DateAdjustment::Previous => const_try_opt!(date.previous_day()),
util::DateAdjustment::Next => const_try_opt!(date.next_day()),
util::DateAdjustment::None => date,
},
time,
})
}
// endregion: checked arithmetic
@ -545,7 +577,13 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn saturating_add(self, duration: Duration) -> Self {
Self(self.0.saturating_add(duration))
if let Some(datetime) = self.checked_add(duration) {
datetime
} else if duration.is_negative() {
Self::MIN
} else {
Self::MAX
}
}
/// Computes `self - duration`, saturating value on overflow.
@ -569,7 +607,13 @@ impl PrimitiveDateTime {
/// );
/// ```
pub const fn saturating_sub(self, duration: Duration) -> Self {
Self(self.0.saturating_sub(duration))
if let Some(datetime) = self.checked_sub(duration) {
datetime
} else if duration.is_negative() {
Self::MAX
} else {
Self::MIN
}
}
// endregion: saturating arithmetic
}
@ -588,7 +632,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_time(self, time: Time) -> Self {
Self(self.0.replace_time(time))
Self {
date: self.date,
time,
}
}
/// Replace the date, preserving the time.
@ -602,7 +649,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_date(self, date: Date) -> Self {
Self(self.0.replace_date(date))
Self {
date,
time: self.time,
}
}
/// Replace the year. The month and day will be unchanged.
@ -618,7 +668,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_year(year))))
Ok(Self {
date: const_try!(self.date.replace_year(year)),
time: self.time,
})
}
/// Replace the month of the year.
@ -634,7 +687,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_month(month))))
Ok(Self {
date: const_try!(self.date.replace_month(month)),
time: self.time,
})
}
/// Replace the day of the month.
@ -650,7 +706,26 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_day(day))))
Ok(Self {
date: const_try!(self.date.replace_day(day)),
time: self.time,
})
}
/// Replace the day of the year.
///
/// ```rust
/// # use time_macros::datetime;
/// assert_eq!(datetime!(2022-049 12:00).replace_ordinal(1), Ok(datetime!(2022-001 12:00)));
/// assert!(datetime!(2022-049 12:00).replace_ordinal(0).is_err()); // 0 isn't a valid ordinal
/// assert!(datetime!(2022-049 12:00).replace_ordinal(366).is_err()); // 2022 isn't a leap year
/// ````
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_ordinal(self, ordinal: u16) -> Result<Self, error::ComponentRange> {
Ok(Self {
date: const_try!(self.date.replace_ordinal(ordinal)),
time: self.time,
})
}
/// Replace the clock hour.
@ -665,7 +740,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_hour(hour))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_hour(hour)),
})
}
/// Replace the minutes within the hour.
@ -680,7 +758,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_minute(minute))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_minute(minute)),
})
}
/// Replace the seconds within the minute.
@ -695,7 +776,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_second(second))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_second(second)),
})
}
/// Replace the milliseconds within the second.
@ -713,7 +797,10 @@ impl PrimitiveDateTime {
self,
millisecond: u16,
) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_millisecond(millisecond))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_millisecond(millisecond)),
})
}
/// Replace the microseconds within the second.
@ -731,7 +818,10 @@ impl PrimitiveDateTime {
self,
microsecond: u32,
) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_microsecond(microsecond))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_microsecond(microsecond)),
})
}
/// Replace the nanoseconds within the second.
@ -746,7 +836,10 @@ impl PrimitiveDateTime {
/// ```
#[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
Ok(Self(const_try!(self.0.replace_nanosecond(nanosecond))))
Ok(Self {
date: self.date,
time: const_try!(self.time.replace_nanosecond(nanosecond)),
})
}
}
// endregion replacement
@ -761,7 +854,7 @@ impl PrimitiveDateTime {
output: &mut impl io::Write,
format: &(impl Formattable + ?Sized),
) -> Result<usize, error::Format> {
self.0.format_into(output, format)
format.format_into(output, Some(self.date), Some(self.time), None)
}
/// Format the `PrimitiveDateTime` using the provided [format
@ -778,7 +871,7 @@ impl PrimitiveDateTime {
/// # Ok::<_, time::Error>(())
/// ```
pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
self.0.format(format)
format.format(Some(self.date), Some(self.time), None)
}
}
@ -801,13 +894,33 @@ impl PrimitiveDateTime {
input: &str,
description: &(impl Parsable + ?Sized),
) -> Result<Self, error::Parse> {
Inner::parse(input, description).map(Self)
description.parse_primitive_date_time(input.as_bytes())
}
}
impl SmartDisplay for PrimitiveDateTime {
type Metadata = ();
fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
let width = smart_display::padded_width_of!(self.date, " ", self.time);
Metadata::new(width, self, ())
}
fn fmt_with_metadata(
&self,
f: &mut fmt::Formatter<'_>,
metadata: Metadata<Self>,
) -> fmt::Result {
f.pad_with_width(
metadata.unpadded_width(),
format_args!("{} {}", self.date, self.time),
)
}
}
impl fmt::Display for PrimitiveDateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
SmartDisplay::fmt(self, f)
}
}
@ -822,64 +935,115 @@ impl fmt::Debug for PrimitiveDateTime {
impl Add<Duration> for PrimitiveDateTime {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, duration: Duration) -> Self::Output {
Self(self.0.add(duration))
self.checked_add(duration)
.expect("resulting value is out of range")
}
}
impl Add<StdDuration> for PrimitiveDateTime {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn add(self, duration: StdDuration) -> Self::Output {
Self(self.0.add(duration))
let (is_next_day, time) = self.time.adjusting_add_std(duration);
Self {
date: if is_next_day {
(self.date + duration)
.next_day()
.expect("resulting value is out of range")
} else {
self.date + duration
},
time,
}
}
}
impl AddAssign<Duration> for PrimitiveDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
fn add_assign(&mut self, duration: Duration) {
self.0.add_assign(duration);
*self = *self + duration;
}
}
impl AddAssign<StdDuration> for PrimitiveDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
fn add_assign(&mut self, duration: StdDuration) {
self.0.add_assign(duration);
*self = *self + duration;
}
}
impl Sub<Duration> for PrimitiveDateTime {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, duration: Duration) -> Self::Output {
Self(self.0.sub(duration))
self.checked_sub(duration)
.expect("resulting value is out of range")
}
}
impl Sub<StdDuration> for PrimitiveDateTime {
type Output = Self;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, duration: StdDuration) -> Self::Output {
Self(self.0.sub(duration))
let (is_previous_day, time) = self.time.adjusting_sub_std(duration);
Self {
date: if is_previous_day {
(self.date - duration)
.previous_day()
.expect("resulting value is out of range")
} else {
self.date - duration
},
time,
}
}
}
impl SubAssign<Duration> for PrimitiveDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub_assign(&mut self, duration: Duration) {
self.0.sub_assign(duration);
*self = *self - duration;
}
}
impl SubAssign<StdDuration> for PrimitiveDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub_assign(&mut self, duration: StdDuration) {
self.0.sub_assign(duration);
*self = *self - duration;
}
}
impl Sub for PrimitiveDateTime {
type Output = Duration;
/// # Panics
///
/// This may panic if an overflow occurs.
fn sub(self, rhs: Self) -> Self::Output {
self.0.sub(rhs.0)
(self.date - rhs.date) + (self.time - rhs.time)
}
}
// endregion trait impls

View file

@ -2,7 +2,7 @@
//!
//! This enables users to write tests such as this, and have test values provided automatically:
//!
//! ```
//! ```ignore
//! # #![allow(dead_code)]
//! use quickcheck::quickcheck;
//! use time::Date;
@ -38,8 +38,6 @@ use alloc::boxed::Box;
use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen};
use crate::convert::*;
use crate::date_time::{DateTime, MaybeOffset};
use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
/// Obtain an arbitrary value between the minimum and maximum inclusive.
@ -73,25 +71,22 @@ impl Arbitrary for Date {
impl Arbitrary for Duration {
fn arbitrary(g: &mut Gen) -> Self {
Self::nanoseconds_i128(arbitrary_between!(
i128;
g,
Self::MIN.whole_nanoseconds(),
Self::MAX.whole_nanoseconds()
))
Self::new_ranged(<_>::arbitrary(g), <_>::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.subsec_nanoseconds(), self.whole_seconds())
(self.subsec_nanoseconds_ranged(), self.whole_seconds())
.shrink()
.map(|(mut nanoseconds, seconds)| {
// Coerce the sign if necessary.
if (seconds > 0 && nanoseconds < 0) || (seconds < 0 && nanoseconds > 0) {
nanoseconds *= -1;
if (seconds > 0 && nanoseconds.get() < 0)
|| (seconds < 0 && nanoseconds.get() > 0)
{
nanoseconds = nanoseconds.neg();
}
Self::new_unchecked(seconds, nanoseconds)
Self::new_ranged_unchecked(seconds, nanoseconds)
}),
)
}
@ -99,20 +94,20 @@ impl Arbitrary for Duration {
impl Arbitrary for Time {
fn arbitrary(g: &mut Gen) -> Self {
Self::__from_hms_nanos_unchecked(
arbitrary_between!(u8; g, 0, Hour.per(Day) - 1),
arbitrary_between!(u8; g, 0, Minute.per(Hour) - 1),
arbitrary_between!(u8; g, 0, Second.per(Minute) - 1),
arbitrary_between!(u32; g, 0, Nanosecond.per(Second) - 1),
Self::from_hms_nanos_ranged(
<_>::arbitrary(g),
<_>::arbitrary(g),
<_>::arbitrary(g),
<_>::arbitrary(g),
)
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.as_hms_nano()
self.as_hms_nano_ranged()
.shrink()
.map(|(hour, minute, second, nanosecond)| {
Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond)
Self::from_hms_nanos_ranged(hour, minute, second, nanosecond)
}),
)
}
@ -120,58 +115,42 @@ impl Arbitrary for Time {
impl Arbitrary for PrimitiveDateTime {
fn arbitrary(g: &mut Gen) -> Self {
Self(<_>::arbitrary(g))
Self::new(<_>::arbitrary(g), <_>::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.0.shrink().map(Self))
Box::new(
(self.date(), self.time())
.shrink()
.map(|(date, time)| Self::new(date, time)),
)
}
}
impl Arbitrary for UtcOffset {
fn arbitrary(g: &mut Gen) -> Self {
let seconds =
arbitrary_between!(i32; g, -(Second.per(Day) as i32 - 1), Second.per(Day) as i32 - 1);
Self::__from_hms_unchecked(
(seconds / Second.per(Hour) as i32) as _,
((seconds % Second.per(Hour) as i32) / Minute.per(Hour) as i32) as _,
(seconds % Second.per(Minute) as i32) as _,
)
Self::from_hms_ranged(<_>::arbitrary(g), <_>::arbitrary(g), <_>::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.as_hms().shrink().map(|(hours, minutes, seconds)| {
Self::__from_hms_unchecked(hours, minutes, seconds)
}),
self.as_hms_ranged()
.shrink()
.map(|(hours, minutes, seconds)| Self::from_hms_ranged(hours, minutes, seconds)),
)
}
}
impl Arbitrary for OffsetDateTime {
fn arbitrary(g: &mut Gen) -> Self {
Self(<_>::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.0.shrink().map(Self))
}
}
impl<O: MaybeOffset + 'static> Arbitrary for DateTime<O> {
fn arbitrary(g: &mut Gen) -> Self {
Self {
date: <_>::arbitrary(g),
time: <_>::arbitrary(g),
offset: <_>::arbitrary(g),
}
Self::new_in_offset(<_>::arbitrary(g), <_>::arbitrary(g), <_>::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.date, self.time, self.offset)
(self.date(), self.time(), self.offset())
.shrink()
.map(|(date, time, offset)| Self { date, time, offset }),
.map(|(date, time, offset)| Self::new_in_offset(date, time, offset)),
)
}
}

View file

@ -3,17 +3,11 @@
use rand::distributions::{Distribution, Standard};
use rand::Rng;
use crate::convert::*;
use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
impl Distribution<Time> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Time {
Time::__from_hms_nanos_unchecked(
rng.gen_range(0..Hour.per(Day)),
rng.gen_range(0..Minute.per(Hour)),
rng.gen_range(0..Second.per(Minute)),
rng.gen_range(0..Nanosecond.per(Second)),
)
Time::from_hms_nanos_ranged(rng.gen(), rng.gen(), rng.gen(), rng.gen())
}
}
@ -27,12 +21,7 @@ impl Distribution<Date> for Standard {
impl Distribution<UtcOffset> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UtcOffset {
let seconds = rng.gen_range(-(Second.per(Day) as i32 - 1)..=(Second.per(Day) as i32 - 1));
UtcOffset::__from_hms_unchecked(
(seconds / Second.per(Hour) as i32) as _,
((seconds % Second.per(Hour) as i32) / Minute.per(Hour) as i32) as _,
(seconds % Second.per(Minute) as i32) as _,
)
UtcOffset::from_hms_ranged(rng.gen(), rng.gen(), rng.gen())
}
}
@ -51,9 +40,7 @@ impl Distribution<OffsetDateTime> for Standard {
impl Distribution<Duration> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Duration {
Duration::nanoseconds_i128(
rng.gen_range(Duration::MIN.whole_nanoseconds()..=Duration::MAX.whole_nanoseconds()),
)
Duration::new_ranged(rng.gen(), rng.gen())
}
}

View file

@ -22,6 +22,8 @@ pub mod rfc3339;
pub mod timestamp;
mod visitor;
#[cfg(feature = "serde-human-readable")]
use alloc::string::ToString;
use core::marker::PhantomData;
#[cfg(feature = "serde-human-readable")]
@ -117,9 +119,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
doc = "use ::serde::Deserialize;"
)]
/// use time::serde;
/// use time::format_description::FormatItem;
/// use time::format_description::BorrowedFormatItem;
///
/// const DATE_TIME_FORMAT: &[FormatItem<'_>] = time::macros::format_description!(
/// const DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = time::macros::format_description!(
/// "hour=[hour], minute=[minute]"
/// );
///
@ -206,18 +208,18 @@ pub use time_macros::serde_format_description as format_description;
use self::visitor::Visitor;
#[cfg(feature = "parsing")]
use crate::format_description::{modifier, Component, FormatItem};
use crate::format_description::{modifier, BorrowedFormatItem, Component};
use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
// region: Date
/// The format used when serializing and deserializing a human-readable `Date`.
#[cfg(feature = "parsing")]
const DATE_FORMAT: &[FormatItem<'_>] = &[
FormatItem::Component(Component::Year(modifier::Year::default())),
FormatItem::Literal(b"-"),
FormatItem::Component(Component::Month(modifier::Month::default())),
FormatItem::Literal(b"-"),
FormatItem::Component(Component::Day(modifier::Day::default())),
const DATE_FORMAT: &[BorrowedFormatItem<'_>] = &[
BorrowedFormatItem::Component(Component::Year(modifier::Year::default())),
BorrowedFormatItem::Literal(b"-"),
BorrowedFormatItem::Component(Component::Month(modifier::Month::default())),
BorrowedFormatItem::Literal(b"-"),
BorrowedFormatItem::Component(Component::Day(modifier::Day::default())),
];
impl Serialize for Date {
@ -275,12 +277,12 @@ impl<'a> Deserialize<'a> for Duration {
// region: OffsetDateTime
/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
#[cfg(feature = "parsing")]
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
FormatItem::Compound(DATE_FORMAT),
FormatItem::Literal(b" "),
FormatItem::Compound(TIME_FORMAT),
FormatItem::Literal(b" "),
FormatItem::Compound(UTC_OFFSET_FORMAT),
const OFFSET_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
BorrowedFormatItem::Compound(DATE_FORMAT),
BorrowedFormatItem::Literal(b" "),
BorrowedFormatItem::Compound(TIME_FORMAT),
BorrowedFormatItem::Literal(b" "),
BorrowedFormatItem::Compound(UTC_OFFSET_FORMAT),
];
impl Serialize for OffsetDateTime {
@ -322,10 +324,10 @@ impl<'a> Deserialize<'a> for OffsetDateTime {
// region: PrimitiveDateTime
/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
#[cfg(feature = "parsing")]
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
FormatItem::Compound(DATE_FORMAT),
FormatItem::Literal(b" "),
FormatItem::Compound(TIME_FORMAT),
const PRIMITIVE_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
BorrowedFormatItem::Compound(DATE_FORMAT),
BorrowedFormatItem::Literal(b" "),
BorrowedFormatItem::Compound(TIME_FORMAT),
];
impl Serialize for PrimitiveDateTime {
@ -364,14 +366,14 @@ impl<'a> Deserialize<'a> for PrimitiveDateTime {
// region: Time
/// The format used when serializing and deserializing a human-readable `Time`.
#[cfg(feature = "parsing")]
const TIME_FORMAT: &[FormatItem<'_>] = &[
FormatItem::Component(Component::Hour(<modifier::Hour>::default())),
FormatItem::Literal(b":"),
FormatItem::Component(Component::Minute(<modifier::Minute>::default())),
FormatItem::Literal(b":"),
FormatItem::Component(Component::Second(<modifier::Second>::default())),
FormatItem::Literal(b"."),
FormatItem::Component(Component::Subsecond(<modifier::Subsecond>::default())),
const TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
BorrowedFormatItem::Component(Component::Hour(modifier::Hour::default())),
BorrowedFormatItem::Literal(b":"),
BorrowedFormatItem::Component(Component::Minute(modifier::Minute::default())),
BorrowedFormatItem::Literal(b":"),
BorrowedFormatItem::Component(Component::Second(modifier::Second::default())),
BorrowedFormatItem::Literal(b"."),
BorrowedFormatItem::Component(Component::Subsecond(modifier::Subsecond::default())),
];
impl Serialize for Time {
@ -402,14 +404,20 @@ impl<'a> Deserialize<'a> for Time {
// region: UtcOffset
/// The format used when serializing and deserializing a human-readable `UtcOffset`.
#[cfg(feature = "parsing")]
const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = &[
FormatItem::Component(Component::OffsetHour(modifier::OffsetHour::default())),
FormatItem::Optional(&FormatItem::Compound(&[
FormatItem::Literal(b":"),
FormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
FormatItem::Optional(&FormatItem::Compound(&[
FormatItem::Literal(b":"),
FormatItem::Component(Component::OffsetSecond(modifier::OffsetSecond::default())),
const UTC_OFFSET_FORMAT: &[BorrowedFormatItem<'_>] = &[
BorrowedFormatItem::Component(Component::OffsetHour({
let mut m = modifier::OffsetHour::default();
m.sign_is_mandatory = true;
m
})),
BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
BorrowedFormatItem::Literal(b":"),
BorrowedFormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
BorrowedFormatItem::Literal(b":"),
BorrowedFormatItem::Component(Component::OffsetSecond(
modifier::OffsetSecond::default(),
)),
])),
])),
];
@ -479,7 +487,7 @@ impl Serialize for Month {
return self.to_string().serialize(serializer);
}
(*self as u8).serialize(serializer)
u8::from(*self).serialize(serializer)
}
}

View file

@ -0,0 +1,63 @@
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with microseconds for
//! the purposes of serde.
//!
//! Use this module in combination with serde's [`#[with]`][with] attribute.
//!
//! When deserializing, the offset is assumed to be UTC.
//!
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
//! [with]: https://serde.rs/field-attrs.html#with
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::OffsetDateTime;
/// Serialize an `OffsetDateTime` as its Unix timestamp with microseconds
pub fn serialize<S: Serializer>(
datetime: &OffsetDateTime,
serializer: S,
) -> Result<S::Ok, S::Error> {
let timestamp = datetime.unix_timestamp_nanos() / 1_000;
timestamp.serialize(serializer)
}
/// Deserialize an `OffsetDateTime` from its Unix timestamp with microseconds
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
let value: i128 = <_>::deserialize(deserializer)?;
OffsetDateTime::from_unix_timestamp_nanos(value * 1_000)
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with microseconds
/// for the purposes of serde.
///
/// Use this module in combination with serde's [`#[with]`][with] attribute.
///
/// When deserializing, the offset is assumed to be UTC.
///
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
/// [with]: https://serde.rs/field-attrs.html#with
pub mod option {
#[allow(clippy::wildcard_imports)]
use super::*;
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with microseconds
pub fn serialize<S: Serializer>(
option: &Option<OffsetDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error> {
option
.map(|timestamp| timestamp.unix_timestamp_nanos() / 1_000)
.serialize(serializer)
}
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with microseconds
pub fn deserialize<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Option<OffsetDateTime>, D::Error> {
Option::deserialize(deserializer)?
.map(|value: i128| OffsetDateTime::from_unix_timestamp_nanos(value * 1_000))
.transpose()
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
}

View file

@ -0,0 +1,63 @@
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with milliseconds for
//! the purposes of serde.
//!
//! Use this module in combination with serde's [`#[with]`][with] attribute.
//!
//! When deserializing, the offset is assumed to be UTC.
//!
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
//! [with]: https://serde.rs/field-attrs.html#with
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::OffsetDateTime;
/// Serialize an `OffsetDateTime` as its Unix timestamp with milliseconds
pub fn serialize<S: Serializer>(
datetime: &OffsetDateTime,
serializer: S,
) -> Result<S::Ok, S::Error> {
let timestamp = datetime.unix_timestamp_nanos() / 1_000_000;
timestamp.serialize(serializer)
}
/// Deserialize an `OffsetDateTime` from its Unix timestamp with milliseconds
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
let value: i128 = <_>::deserialize(deserializer)?;
OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000)
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with milliseconds
/// for the purposes of serde.
///
/// Use this module in combination with serde's [`#[with]`][with] attribute.
///
/// When deserializing, the offset is assumed to be UTC.
///
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
/// [with]: https://serde.rs/field-attrs.html#with
pub mod option {
#[allow(clippy::wildcard_imports)]
use super::*;
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with milliseconds
pub fn serialize<S: Serializer>(
option: &Option<OffsetDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error> {
option
.map(|timestamp| timestamp.unix_timestamp_nanos() / 1_000_000)
.serialize(serializer)
}
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with milliseconds
pub fn deserialize<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Option<OffsetDateTime>, D::Error> {
Option::deserialize(deserializer)?
.map(|value: i128| OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000))
.transpose()
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
}

View file

@ -7,6 +7,10 @@
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
//! [with]: https://serde.rs/field-attrs.html#with
pub mod microseconds;
pub mod milliseconds;
pub mod nanoseconds;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::OffsetDateTime;

View file

@ -0,0 +1,61 @@
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with nanoseconds for
//! the purposes of serde.
//!
//! Use this module in combination with serde's [`#[with]`][with] attribute.
//!
//! When deserializing, the offset is assumed to be UTC.
//!
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
//! [with]: https://serde.rs/field-attrs.html#with
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::OffsetDateTime;
/// Serialize an `OffsetDateTime` as its Unix timestamp with nanoseconds
pub fn serialize<S: Serializer>(
datetime: &OffsetDateTime,
serializer: S,
) -> Result<S::Ok, S::Error> {
datetime.unix_timestamp_nanos().serialize(serializer)
}
/// Deserialize an `OffsetDateTime` from its Unix timestamp with nanoseconds
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
OffsetDateTime::from_unix_timestamp_nanos(<_>::deserialize(deserializer)?)
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with nanoseconds
/// for the purposes of serde.
///
/// Use this module in combination with serde's [`#[with]`][with] attribute.
///
/// When deserializing, the offset is assumed to be UTC.
///
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
/// [with]: https://serde.rs/field-attrs.html#with
pub mod option {
#[allow(clippy::wildcard_imports)]
use super::*;
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with nanoseconds
pub fn serialize<S: Serializer>(
option: &Option<OffsetDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error> {
option
.map(OffsetDateTime::unix_timestamp_nanos)
.serialize(serializer)
}
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with nanoseconds
pub fn deserialize<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Option<OffsetDateTime>, D::Error> {
Option::deserialize(deserializer)?
.map(OffsetDateTime::from_unix_timestamp_nanos)
.transpose()
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
}
}

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