Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d1c55a454051c83e8fd2ad8431cf37b90dd6ea31"
},
"path_in_vcs": "time"
}
+1106
View File
File diff suppressed because it is too large Load Diff
+341
View File
@@ -0,0 +1,341 @@
# 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 = "2024"
rust-version = "1.88.0"
name = "time"
version = "0.3.54"
authors = [
"Jacob Pratt <open-source@jhpratt.dev>",
"Time contributors",
]
build = false
include = [
"{src,tests,benchmarks}/**/*",
"LICENSE-*",
"README.md",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]."
homepage = "https://time-rs.github.io"
readme = "README.md"
keywords = [
"date",
"time",
"calendar",
"duration",
]
categories = [
"date-and-time",
"no-std",
"parser-implementations",
"value-formatting",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/time-rs/time"
[package.metadata.docs.rs]
all-features = true
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = ["--generate-link-to-definition"]
[features]
alloc = ["serde_core?/alloc"]
default = ["std"]
formatting = [
"std",
"time-macros?/formatting",
]
large-dates = [
"time-core/large-dates",
"time-macros?/large-dates",
]
local-offset = [
"std",
"dep:libc",
"dep:num_threads",
]
macros = ["dep:time-macros"]
parsing = ["time-macros?/parsing"]
quickcheck = [
"dep:quickcheck",
"alloc",
"deranged/quickcheck",
]
rand = [
"rand08",
"rand09",
"rand010",
]
rand010 = [
"dep:rand010",
"deranged/rand010",
]
rand08 = [
"dep:rand08",
"deranged/rand08",
]
rand09 = [
"dep:rand09",
"deranged/rand09",
]
serde = [
"dep:serde_core",
"time-macros?/serde",
"deranged/serde",
]
serde-human-readable = [
"serde",
"formatting",
"parsing",
]
serde-well-known = [
"serde",
"formatting",
"parsing",
]
std = ["alloc"]
wasm-bindgen = ["dep:js-sys"]
[lib]
name = "time"
path = "src/lib.rs"
bench = false
[[test]]
name = "tests"
path = "tests/integration/main.rs"
[[bench]]
name = "benchmarks"
path = "benchmarks/main.rs"
harness = false
[dependencies.deranged]
version = "0.5.8"
[dependencies.num-conv]
version = "0.2.2"
[dependencies.powerfmt]
version = "0.2.0"
default-features = false
[dependencies.quickcheck]
version = "1.0.3"
optional = true
default-features = false
[dependencies.rand010]
version = "0.10.1"
optional = true
default-features = false
package = "rand"
[dependencies.rand08]
version = "0.8.6"
optional = true
default-features = false
package = "rand"
[dependencies.rand09]
version = "0.9.4"
optional = true
default-features = false
package = "rand"
[dependencies.serde_core]
version = "1.0.220"
optional = true
default-features = false
[dependencies.time-core]
version = "=0.1.9"
[dependencies.time-macros]
version = "=0.2.32"
optional = true
[dev-dependencies.num-conv]
version = "0.2.2"
[dev-dependencies.quickcheck_macros]
version = "1.0.0"
[dev-dependencies.rand08]
version = "0.8.6"
default-features = false
package = "rand"
[dev-dependencies.rand09]
version = "0.9.4"
features = ["small_rng"]
default-features = false
package = "rand"
[dev-dependencies.rstest]
version = "0.26.1"
default-features = false
[dev-dependencies.rstest_reuse]
version = "0.7.0"
[dev-dependencies.serde]
version = "1.0.184"
features = [
"derive",
"alloc",
]
default-features = false
[dev-dependencies.serde_json]
version = "1.0.68"
[dev-dependencies.serde_test2]
version = "2.0.0"
[dev-dependencies.time-macros]
version = "=0.2.32"
[target."cfg(__ui_tests)".dev-dependencies.trybuild]
version = "1.0.102"
[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.js-sys]
version = "0.3.58"
optional = true
[target."cfg(bench)".dev-dependencies.criterion]
version = "0.8.1"
default-features = false
[target.'cfg(target_family = "unix")'.dependencies.libc]
version = "0.2.98"
optional = true
[target.'cfg(target_family = "unix")'.dependencies.num_threads]
version = "0.1.2"
optional = true
[lints.clippy]
alloc-instead-of-core = "deny"
as-underscore = "warn"
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"
missing-panics-doc = "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.all]
level = "warn"
priority = -1
[lints.clippy.incompatible-msrv]
level = "allow"
priority = 1
[lints.clippy.nursery]
level = "warn"
priority = -1
[lints.clippy.option-if-let-else]
level = "allow"
priority = 1
[lints.clippy.redundant-pub-crate]
level = "allow"
priority = 1
[lints.clippy.uninhabited-references]
level = "allow"
priority = 1
[lints.rust]
ambiguous-glob-reexports = "deny"
clashing-extern-declarations = "deny"
const-item-mutation = "deny"
dangling-pointers-from-temporaries = "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"
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-import-braces = "warn"
unused-lifetimes = "warn"
unused-qualifications = "warn"
variant-size-differences = "warn"
[lints.rust.unexpected_cfgs]
level = "deny"
priority = 0
check-cfg = [
"cfg(__ui_tests)",
"cfg(bench)",
]
[lints.rust.unstable-name-collisions]
level = "allow"
priority = 1
[lints.rust.unused]
level = "warn"
priority = -1
[lints.rustdoc]
private-doc-tests = "warn"
unescaped-backticks = "warn"
+98
View File
@@ -0,0 +1,98 @@
lints.workspace = true
[package]
name = "time"
version = "0.3.54"
categories = [
"date-and-time",
"no-std",
"parser-implementations",
"value-formatting",
]
description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]."
include = ["{src,tests,benchmarks}/**/*", "LICENSE-*", "README.md"]
readme = "../README.md"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
all-features = true
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = ["--generate-link-to-definition"]
[lib]
bench = false
[features]
default = ["std"]
alloc = ["serde_core?/alloc"]
formatting = ["std", "time-macros?/formatting"]
large-dates = ["time-core/large-dates", "time-macros?/large-dates"]
local-offset = ["std", "dep:libc", "dep:num_threads"]
macros = ["dep:time-macros"]
parsing = ["time-macros?/parsing"]
quickcheck = ["dep:quickcheck", "alloc", "deranged/quickcheck"]
rand = ["rand08", "rand09", "rand010"]
rand08 = ["dep:rand08", "deranged/rand08"]
rand09 = ["dep:rand09", "deranged/rand09"]
rand010 = ["dep:rand010", "deranged/rand010"]
serde = ["dep:serde_core", "time-macros?/serde", "deranged/serde"]
serde-human-readable = ["serde", "formatting", "parsing"]
# Deprecated in favor of using the relevant flags directly.
serde-well-known = ["serde", "formatting", "parsing"]
std = ["alloc"]
wasm-bindgen = ["dep:js-sys"]
# If adding an optional dependency, be sure to use the `dep:` prefix above to avoid an implicit
# feature gate.
[dependencies]
deranged.workspace = true
num-conv.workspace = true
powerfmt.workspace = true
quickcheck = { workspace = true, optional = true }
rand08 = { workspace = true, optional = true }
rand09 = { workspace = true, optional = true }
rand010 = { workspace = true, optional = true }
serde_core = { workspace = true, optional = true }
time-core.workspace = true
time-macros = { workspace = true, optional = true }
[target.'cfg(target_family = "unix")'.dependencies]
libc = { workspace = true, optional = true }
num_threads = { workspace = true, optional = true }
[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies]
js-sys = { workspace = true, optional = true }
[dev-dependencies]
num-conv.workspace = true
rand08.workspace = true
rand09 = { workspace = true, features = ["small_rng"] }
serde = { workspace = true, features = ["derive", "alloc"] }
serde_json.workspace = true
serde_test2.workspace = true
quickcheck_macros.workspace = true
time-macros.workspace = true
rstest.workspace = true
rstest_reuse.workspace = true
[target.'cfg(__ui_tests)'.dev-dependencies]
trybuild.workspace = true
[target.'cfg(bench)'.dev-dependencies]
criterion.workspace = true
[[bench]]
name = "benchmarks"
harness = false
path = "benchmarks/main.rs"
[[test]]
name = "tests"
path = "tests/integration/main.rs"
+177
View File
@@ -0,0 +1,177 @@
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
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 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.
+51
View File
@@ -0,0 +1,51 @@
# time
[![minimum rustc: 1.88.0](https://img.shields.io/badge/minimum%20rustc-1.88.0-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)
Documentation:
- [latest release](https://docs.rs/time)
- [main branch](https://time-rs.github.io/api/time)
- [book](https://time-rs.github.io/book)
## Minimum Rust version policy
`time` 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 `time` 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 `time`. This does not mean that the latest
version of the third-party crate supports the minimum supported Rust version of `time`.
## 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.
Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time).
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/time-rs/time/discussions
## License
This project is licensed under either of
- [Apache License, Version 2.0](https://github.com/time-rs/time/blob/main/LICENSE-Apache)
- [MIT license](https://github.com/time-rs/time/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.
+354
View File
@@ -0,0 +1,354 @@
#![expect(
clippy::large_stack_frames,
reason = "iterating over large array; does not cause stack overflow"
)]
use std::hint::black_box as bb;
use std::sync::LazyLock;
use criterion::Bencher;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::date;
use time::{Date, Time};
/// Generate a representative sample of all dates.
///
/// The ratio of month sizes, week sizes, year sign, leap years, etc. are all identical to the full
/// range. This ensures that benchmarks accurately reflect random data.
//
// Note that this is a _very_ large array (over 1 MiB), so we silence the warning about large stack
// frames at the top of this file.
fn representative_dates() -> [Date; 292_194] {
static DATES: LazyLock<[Date; 292_194]> = LazyLock::new(|| {
let mut dates = [Date::MIN; _];
for (i, date) in date!(-0400-01-01).iter_to(date!(0399-12-31)).enumerate() {
dates[i] = date;
}
crate::shuffle(dates)
});
*DATES
}
setup_benchmark! {
"Date",
fn noop(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(date);
}
});
}
fn noop_windows(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates().windows(2) {
let first = date[0];
let second = date[1];
let _ = bb((bb(first), bb(second)));
}
});
}
fn from_calendar_date(ben: &mut Bencher<'_>) {
let dates = representative_dates().map(Date::to_calendar_date);
ben.iter(|| {
for (year, month, day) in dates {
let _ = bb(Date::from_calendar_date(bb(year), bb(month), bb(day)));
}
});
}
fn from_ordinal_date(ben: &mut Bencher<'_>) {
let dates = representative_dates().map(Date::to_ordinal_date);
ben.iter(|| {
for (year, ordinal) in dates {
let _ = bb(Date::from_ordinal_date(bb(year), bb(ordinal)));
}
});
}
fn from_iso_week_date(ben: &mut Bencher<'_>) {
let dates = representative_dates().map(Date::to_iso_week_date);
ben.iter(|| {
for (year, week, weekday) in dates {
let _ = bb(Date::from_iso_week_date(bb(year), bb(week), bb(weekday)));
}
});
}
fn from_julian_day(ben: &mut Bencher<'_>) {
let dates = representative_dates().map(Date::to_julian_day);
ben.iter(|| {
for julian_day in dates {
let _ = bb(Date::from_julian_day(bb(julian_day)));
}
});
}
fn year(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).year());
}
});
}
fn month(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).month());
}
});
}
fn day(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).day());
}
});
}
fn ordinal(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).ordinal());
}
});
}
fn iso_week(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).iso_week());
}
});
}
fn sunday_based_week(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).sunday_based_week());
}
});
}
fn monday_based_week(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).monday_based_week());
}
});
}
fn to_calendar_date(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).to_calendar_date());
}
});
}
fn to_ordinal_date(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).to_ordinal_date());
}
});
}
fn to_iso_week_date(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).to_iso_week_date());
}
});
}
fn weekday(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).weekday());
}
});
}
fn next_day(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).next_day());
}
});
}
fn previous_day(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).previous_day());
}
});
}
fn to_julian_day(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).to_julian_day());
}
});
}
fn midnight(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).midnight());
}
});
}
fn with_time(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).with_time(bb(Time::MIDNIGHT)));
}
});
}
fn with_hms(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).with_hms(bb(0), bb(0), bb(0)));
}
});
}
fn with_hms_milli(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).with_hms_milli(bb(0), bb(0), bb(0), bb(0)));
}
});
}
fn with_hms_micro(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).with_hms_micro(bb(0), bb(0), bb(0), bb(0)));
}
});
}
fn with_hms_nano(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date).with_hms_nano(bb(0), bb(0), bb(0), bb(0)));
}
});
}
fn add(ben: &mut Bencher<'_>) {
let dt = 5.days();
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date) + bb(dt));
}
});
}
fn add_std(ben: &mut Bencher<'_>) {
let dt = 5.std_days();
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date) - bb(dt));
}
});
}
fn add_assign(ben: &mut Bencher<'_>) {
let dt = 1.days();
ben.iter(|| {
for mut date in representative_dates() {
date += bb(dt);
let _ = bb(date);
}
});
}
fn add_assign_std(ben: &mut Bencher<'_>) {
let dt = 1.std_days();
ben.iter(|| {
for mut date in representative_dates() {
date += bb(dt);
let _ = bb(date);
}
});
}
fn sub(ben: &mut Bencher<'_>) {
let dt = 5.days();
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date) - bb(dt));
}
});
}
fn sub_std(ben: &mut Bencher<'_>) {
let dt = 5.std_days();
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date) - bb(dt));
}
});
}
fn sub_assign(ben: &mut Bencher<'_>) {
let dt = 1.days();
ben.iter(|| {
for mut date in representative_dates() {
date -= bb(dt);
let _ = bb(date);
}
});
}
fn sub_assign_std(ben: &mut Bencher<'_>) {
let dt = 1.std_days();
ben.iter(|| {
for mut date in representative_dates() {
date -= bb(dt);
let _ = bb(date);
}
});
}
fn sub_self(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates() {
let _ = bb(bb(date) - bb(date));
}
});
}
fn partial_ord(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates().windows(2) {
let first = date[0];
let second = date[1];
let _ = bb(bb(first).partial_cmp(&bb(second)));
}
});
}
fn ord(ben: &mut Bencher<'_>) {
ben.iter(|| {
for date in representative_dates().windows(2) {
let first = date[0];
let second = date[1];
let _ = bb(bb(first).cmp(&bb(second)));
}
});
}
}
+714
View File
@@ -0,0 +1,714 @@
use std::time::Duration as StdDuration;
use criterion::Bencher;
use time::SignedDuration;
use time::ext::{NumericalDuration, NumericalStdDuration};
setup_benchmark! {
"SignedDuration",
fn is_zero(ben: &mut Bencher<'_>) {
let a = (-1).nanoseconds();
let b = 0.seconds();
let c = 1.nanoseconds();
ben.iter(|| a.is_zero());
ben.iter(|| b.is_zero());
ben.iter(|| c.is_zero());
}
fn is_negative(ben: &mut Bencher<'_>) {
let a = (-1).seconds();
let b = 0.seconds();
let c = 1.seconds();
ben.iter(|| a.is_negative());
ben.iter(|| b.is_negative());
ben.iter(|| c.is_negative());
}
fn is_positive(ben: &mut Bencher<'_>) {
let a = (-1).seconds();
let b = 0.seconds();
let c = 1.seconds();
ben.iter(|| a.is_positive());
ben.iter(|| b.is_positive());
ben.iter(|| c.is_positive());
}
fn abs(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 0.seconds();
let c = (-1).seconds();
ben.iter(|| a.abs());
ben.iter(|| b.abs());
ben.iter(|| c.abs());
}
fn unsigned_abs(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 0.seconds();
let c = (-1).seconds();
ben.iter(|| a.unsigned_abs());
ben.iter(|| b.unsigned_abs());
ben.iter(|| c.unsigned_abs());
}
fn new(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::new(1, 0));
ben.iter(|| SignedDuration::new(-1, 0));
ben.iter(|| SignedDuration::new(1, 2_000_000_000));
ben.iter(|| SignedDuration::new(0, 0));
ben.iter(|| SignedDuration::new(0, 1_000_000_000));
ben.iter(|| SignedDuration::new(-1, 1_000_000_000));
ben.iter(|| SignedDuration::new(-2, 1_000_000_000));
ben.iter(|| SignedDuration::new(1, -1));
ben.iter(|| SignedDuration::new(-1, 1));
ben.iter(|| SignedDuration::new(1, 1));
ben.iter(|| SignedDuration::new(-1, -1));
ben.iter(|| SignedDuration::new(0, 1));
ben.iter(|| SignedDuration::new(0, -1));
ben.iter(|| SignedDuration::new(-1, 1_400_000_000));
ben.iter(|| SignedDuration::new(-2, 1_400_000_000));
ben.iter(|| SignedDuration::new(-3, 1_400_000_000));
ben.iter(|| SignedDuration::new(1, -1_400_000_000));
ben.iter(|| SignedDuration::new(2, -1_400_000_000));
ben.iter(|| SignedDuration::new(3, -1_400_000_000));
}
fn weeks(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::weeks(1));
ben.iter(|| SignedDuration::weeks(2));
ben.iter(|| SignedDuration::weeks(-1));
ben.iter(|| SignedDuration::weeks(-2));
}
fn days(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::days(1));
ben.iter(|| SignedDuration::days(2));
ben.iter(|| SignedDuration::days(-1));
ben.iter(|| SignedDuration::days(-2));
}
fn hours(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::hours(1));
ben.iter(|| SignedDuration::hours(2));
ben.iter(|| SignedDuration::hours(-1));
ben.iter(|| SignedDuration::hours(-2));
}
fn minutes(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::minutes(1));
ben.iter(|| SignedDuration::minutes(2));
ben.iter(|| SignedDuration::minutes(-1));
ben.iter(|| SignedDuration::minutes(-2));
}
fn seconds(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::seconds(1));
ben.iter(|| SignedDuration::seconds(2));
ben.iter(|| SignedDuration::seconds(-1));
ben.iter(|| SignedDuration::seconds(-2));
}
fn seconds_f64(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::seconds_f64(0.5));
ben.iter(|| SignedDuration::seconds_f64(-0.5));
}
fn seconds_f32(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::seconds_f32(0.5));
ben.iter(|| SignedDuration::seconds_f32(-0.5));
}
fn saturating_seconds_f64(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::saturating_seconds_f64(0.5));
ben.iter(|| SignedDuration::saturating_seconds_f64(-0.5));
}
fn saturating_seconds_f32(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::saturating_seconds_f32(0.5));
ben.iter(|| SignedDuration::saturating_seconds_f32(-0.5));
}
fn checked_seconds_f64(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::checked_seconds_f64(0.5));
ben.iter(|| SignedDuration::checked_seconds_f64(-0.5));
}
fn checked_seconds_f32(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::checked_seconds_f32(0.5));
ben.iter(|| SignedDuration::checked_seconds_f32(-0.5));
}
fn milliseconds(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::milliseconds(1));
ben.iter(|| SignedDuration::milliseconds(-1));
}
fn microseconds(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::microseconds(1));
ben.iter(|| SignedDuration::microseconds(-1));
}
fn nanoseconds(ben: &mut Bencher<'_>) {
ben.iter(|| SignedDuration::nanoseconds(1));
ben.iter(|| SignedDuration::nanoseconds(-1));
}
fn whole_weeks(ben: &mut Bencher<'_>) {
let a = SignedDuration::weeks(1);
let b = SignedDuration::weeks(-1);
let c = SignedDuration::days(6);
let d = SignedDuration::days(-6);
ben.iter(|| a.whole_weeks());
ben.iter(|| b.whole_weeks());
ben.iter(|| c.whole_weeks());
ben.iter(|| d.whole_weeks());
}
fn whole_days(ben: &mut Bencher<'_>) {
let a = SignedDuration::days(1);
let b = SignedDuration::days(-1);
let c = SignedDuration::hours(23);
let d = SignedDuration::hours(-23);
ben.iter(|| a.whole_days());
ben.iter(|| b.whole_days());
ben.iter(|| c.whole_days());
ben.iter(|| d.whole_days());
}
fn whole_hours(ben: &mut Bencher<'_>) {
let a = SignedDuration::hours(1);
let b = SignedDuration::hours(-1);
let c = SignedDuration::minutes(59);
let d = SignedDuration::minutes(-59);
ben.iter(|| a.whole_hours());
ben.iter(|| b.whole_hours());
ben.iter(|| c.whole_hours());
ben.iter(|| d.whole_hours());
}
fn whole_minutes(ben: &mut Bencher<'_>) {
let a = 1.minutes();
let b = (-1).minutes();
let c = 59.seconds();
let d = (-59).seconds();
ben.iter(|| a.whole_minutes());
ben.iter(|| b.whole_minutes());
ben.iter(|| c.whole_minutes());
ben.iter(|| d.whole_minutes());
}
fn whole_seconds(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = (-1).seconds();
let c = 1.minutes();
let d = (-1).minutes();
ben.iter(|| a.whole_seconds());
ben.iter(|| b.whole_seconds());
ben.iter(|| c.whole_seconds());
ben.iter(|| d.whole_seconds());
}
fn as_seconds_f64(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = (-1).seconds();
let c = 1.minutes();
let d = (-1).minutes();
let e = 1.5.seconds();
let f = (-1.5).seconds();
ben.iter(|| a.as_seconds_f64());
ben.iter(|| b.as_seconds_f64());
ben.iter(|| c.as_seconds_f64());
ben.iter(|| d.as_seconds_f64());
ben.iter(|| e.as_seconds_f64());
ben.iter(|| f.as_seconds_f64());
}
fn as_seconds_f32(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = (-1).seconds();
let c = 1.minutes();
let d = (-1).minutes();
let e = 1.5.seconds();
let f = (-1.5).seconds();
ben.iter(|| a.as_seconds_f32());
ben.iter(|| b.as_seconds_f32());
ben.iter(|| c.as_seconds_f32());
ben.iter(|| d.as_seconds_f32());
ben.iter(|| e.as_seconds_f32());
ben.iter(|| f.as_seconds_f32());
}
fn whole_milliseconds(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = (-1).seconds();
let c = 1.milliseconds();
let d = (-1).milliseconds();
ben.iter(|| a.whole_milliseconds());
ben.iter(|| b.whole_milliseconds());
ben.iter(|| c.whole_milliseconds());
ben.iter(|| d.whole_milliseconds());
}
fn subsec_milliseconds(ben: &mut Bencher<'_>) {
let a = 1.4.seconds();
let b = (-1.4).seconds();
ben.iter(|| a.subsec_milliseconds());
ben.iter(|| b.subsec_milliseconds());
}
fn whole_microseconds(ben: &mut Bencher<'_>) {
let a = 1.milliseconds();
let b = (-1).milliseconds();
let c = 1.microseconds();
let d = (-1).microseconds();
ben.iter(|| a.whole_microseconds());
ben.iter(|| b.whole_microseconds());
ben.iter(|| c.whole_microseconds());
ben.iter(|| d.whole_microseconds());
}
fn subsec_microseconds(ben: &mut Bencher<'_>) {
let a = 1.0004.seconds();
let b = (-1.0004).seconds();
ben.iter(|| a.subsec_microseconds());
ben.iter(|| b.subsec_microseconds());
}
fn whole_nanoseconds(ben: &mut Bencher<'_>) {
let a = 1.microseconds();
let b = (-1).microseconds();
let c = 1.nanoseconds();
let d = (-1).nanoseconds();
ben.iter(|| a.whole_nanoseconds());
ben.iter(|| b.whole_nanoseconds());
ben.iter(|| c.whole_nanoseconds());
ben.iter(|| d.whole_nanoseconds());
}
fn subsec_nanoseconds(ben: &mut Bencher<'_>) {
let a = 1.000_000_4.seconds();
let b = (-1.000_000_4).seconds();
ben.iter(|| a.subsec_nanoseconds());
ben.iter(|| b.subsec_nanoseconds());
}
fn checked_add(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = SignedDuration::MAX;
let c = (-5).seconds();
let a2 = 5.seconds();
let b2 = 1.nanoseconds();
let c2 = 5.seconds();
ben.iter(|| a.checked_add(a2));
ben.iter(|| b.checked_add(b2));
ben.iter(|| c.checked_add(c2));
}
fn checked_sub(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = SignedDuration::MIN;
let c = 5.seconds();
let a2 = 5.seconds();
let b2 = 1.nanoseconds();
let c2 = 10.seconds();
ben.iter(|| a.checked_sub(a2));
ben.iter(|| b.checked_sub(b2));
ben.iter(|| c.checked_sub(c2));
}
fn checked_mul(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = SignedDuration::MAX;
ben.iter(|| a.checked_mul(2));
ben.iter(|| b.checked_mul(2));
}
fn checked_div(ben: &mut Bencher<'_>) {
let a = 10.seconds();
ben.iter(|| a.checked_div(2));
ben.iter(|| a.checked_div(0));
}
fn saturating_add(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = SignedDuration::MAX;
let c = SignedDuration::MIN;
let d = (-5).seconds();
let a2 = 5.seconds();
let b2 = 1.nanoseconds();
let c2 = (-1).nanoseconds();
let d2 = 5.seconds();
ben.iter(|| a.saturating_add(a2));
ben.iter(|| b.saturating_add(b2));
ben.iter(|| c.saturating_add(c2));
ben.iter(|| d.saturating_add(d2));
}
fn saturating_sub(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = SignedDuration::MIN;
let c = SignedDuration::MAX;
let d = 5.seconds();
let a2 = 5.seconds();
let b2 = 1.nanoseconds();
let c2 = (-1).nanoseconds();
let d2 = 10.seconds();
ben.iter(|| a.saturating_sub(a2));
ben.iter(|| b.saturating_sub(b2));
ben.iter(|| c.saturating_sub(c2));
ben.iter(|| d.saturating_sub(d2));
}
fn saturating_mul(ben: &mut Bencher<'_>) {
let a = 5.seconds();
let b = 5.seconds();
let c = 5.seconds();
let d = SignedDuration::MAX;
let e = SignedDuration::MIN;
let f = SignedDuration::MAX;
let g = SignedDuration::MIN;
ben.iter(|| a.saturating_mul(2));
ben.iter(|| b.saturating_mul(-2));
ben.iter(|| c.saturating_mul(0));
ben.iter(|| d.saturating_mul(2));
ben.iter(|| e.saturating_mul(2));
ben.iter(|| f.saturating_mul(-2));
ben.iter(|| g.saturating_mul(-2));
}
fn try_from_std_duration(ben: &mut Bencher<'_>) {
let a = 0.std_seconds();
let b = 1.std_seconds();
ben.iter(|| SignedDuration::try_from(a));
ben.iter(|| SignedDuration::try_from(b));
}
fn try_to_std_duration(ben: &mut Bencher<'_>) {
let a = 0.seconds();
let b = 1.seconds();
let c = (-1).seconds();
ben.iter(|| StdDuration::try_from(a));
ben.iter(|| StdDuration::try_from(b));
ben.iter(|| StdDuration::try_from(c));
}
fn add(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 2.seconds();
let c = 500.milliseconds();
let d = (-1).seconds();
ben.iter(|| a + b + c + d);
}
fn add_std(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 2.std_seconds();
ben.iter(|| a + b);
}
fn std_add(ben: &mut Bencher<'_>) {
let a = 1.std_seconds();
let b = 2.seconds();
ben.iter(|| a + b);
}
fn add_assign(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 500.milliseconds();
let c = (-1).seconds();
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration += a,
|duration| *duration += b,
|duration| *duration += c,
]
);
}
fn add_assign_std(ben: &mut Bencher<'_>) {
let a = 1.std_seconds();
let b = 500.std_milliseconds();
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration += a,
|duration| *duration += b,
]
);
}
fn neg(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = (-1).seconds();
let c = 0.seconds();
ben.iter(|| -a);
ben.iter(|| -b);
ben.iter(|| -c);
}
fn sub(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 1.seconds();
let c = 1_500.milliseconds();
let d = 500.milliseconds();
let e = 1.seconds();
let f = (-1).seconds();
ben.iter(|| a - b);
ben.iter(|| b - c);
ben.iter(|| c - d);
ben.iter(|| d - e);
ben.iter(|| e - f);
ben.iter(|| f - a);
}
fn sub_std(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 2.std_seconds();
ben.iter(|| a - b);
}
fn std_sub(ben: &mut Bencher<'_>) {
let a = 1.std_seconds();
let b = 2.seconds();
ben.iter(|| a - b);
}
fn sub_assign(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 500.milliseconds();
let c = (-1).seconds();
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration -= a,
|duration| *duration -= b,
|duration| *duration -= c,
]
);
}
fn mul_int(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| d * 2);
ben.iter(|| d * -2);
}
fn mul_int_assign(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration *= 2,
|duration| *duration *= -2,
]
);
}
fn int_mul(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| 2 * d);
ben.iter(|| -2 * d);
}
fn div_int(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| d / 2);
ben.iter(|| d / -2);
}
fn div_int_assign(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration /= 2,
|duration| *duration /= -2,
]
);
}
fn div(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 0.5.seconds();
ben.iter(|| a / b);
}
fn mul_float(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| d * 1.5_f32);
ben.iter(|| d * 2.5_f32);
ben.iter(|| d * -1.5_f32);
ben.iter(|| d * 0_f32);
ben.iter(|| d * 1.5_f64);
ben.iter(|| d * 2.5_f64);
ben.iter(|| d * -1.5_f64);
ben.iter(|| d * 0_f64);
}
fn float_mul(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| 1.5_f32 * d);
ben.iter(|| 2.5_f32 * d);
ben.iter(|| -1.5_f32 * d);
ben.iter(|| 0_f32 * d);
ben.iter(|| 1.5_f64 * d);
ben.iter(|| 2.5_f64 * d);
ben.iter(|| -1.5_f64 * d);
ben.iter(|| 0_f64 * d);
}
fn mul_float_assign(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| 1.seconds(),
[
|duration| *duration *= 1.5_f32,
|duration| *duration *= 2.5_f32,
|duration| *duration *= -1.5_f32,
|duration| *duration *= 3.15_f32,
|duration| *duration *= 1.5_f64,
|duration| *duration *= 2.5_f64,
|duration| *duration *= -1.5_f64,
|duration| *duration *= 0_f64,
]
);
}
fn div_float(ben: &mut Bencher<'_>) {
let d = 1.seconds();
ben.iter(|| d / 1_f32);
ben.iter(|| d / 2_f32);
ben.iter(|| d / -1_f32);
ben.iter(|| d / 1_f64);
ben.iter(|| d / 2_f64);
ben.iter(|| d / -1_f64);
}
fn div_float_assign(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| 10.seconds(),
[
|duration| *duration /= 1_f32,
|duration| *duration /= 2_f32,
|duration| *duration /= -1_f32,
|duration| *duration /= 1_f64,
|duration| *duration /= 2_f64,
|duration| *duration /= -1_f64,
]
);
}
fn partial_eq(ben: &mut Bencher<'_>) {
let a = 1.minutes();
let b = (-1).minutes();
let c = 40.seconds();
ben.iter(|| a == b);
ben.iter(|| c == a);
}
fn partial_eq_std(ben: &mut Bencher<'_>) {
let a = (-1).seconds();
let b = 1.std_seconds();
let c = (-1).minutes();
let d = 1.std_minutes();
let e = 40.seconds();
ben.iter(|| a == b);
ben.iter(|| c == d);
ben.iter(|| e == d);
}
fn std_partial_eq(ben: &mut Bencher<'_>) {
let a = 1.std_seconds();
let b = (-1).seconds();
let c = 1.std_minutes();
let d = (-1).minutes();
let e = 40.std_seconds();
let f = 1.minutes();
ben.iter(|| a == b);
ben.iter(|| c == d);
ben.iter(|| e == f);
}
fn partial_ord(ben: &mut Bencher<'_>) {
let a = 0.seconds();
let b = 1.seconds();
let c = (-1).seconds();
let d = 1.minutes();
let e = (-1).minutes();
ben.iter(|| a.partial_cmp(&a));
ben.iter(|| b.partial_cmp(&a));
ben.iter(|| b.partial_cmp(&c));
ben.iter(|| c.partial_cmp(&b));
ben.iter(|| a.partial_cmp(&c));
ben.iter(|| a.partial_cmp(&b));
ben.iter(|| c.partial_cmp(&a));
ben.iter(|| d.partial_cmp(&b));
ben.iter(|| e.partial_cmp(&c));
}
fn partial_ord_std(ben: &mut Bencher<'_>) {
let a = 0.seconds();
let b = 0.std_seconds();
let c = 1.seconds();
let d = (-1).seconds();
let e = 1.std_seconds();
let f = 1.minutes();
let g = u64::MAX.std_seconds();
ben.iter(|| a.partial_cmp(&b));
ben.iter(|| c.partial_cmp(&b));
ben.iter(|| d.partial_cmp(&e));
ben.iter(|| a.partial_cmp(&e));
ben.iter(|| d.partial_cmp(&b));
ben.iter(|| f.partial_cmp(&e));
ben.iter(|| a.partial_cmp(&g));
}
fn std_partial_ord(ben: &mut Bencher<'_>) {
let a = 0.std_seconds();
let b = 0.seconds();
let c = 1.std_seconds();
let d = (-1).seconds();
let e = 1.seconds();
let f = 1.std_minutes();
ben.iter(|| a.partial_cmp(&b));
ben.iter(|| c.partial_cmp(&b));
ben.iter(|| c.partial_cmp(&d));
ben.iter(|| a.partial_cmp(&d));
ben.iter(|| a.partial_cmp(&e));
ben.iter(|| f.partial_cmp(&e));
}
fn ord(ben: &mut Bencher<'_>) {
let a = 1.seconds();
let b = 0.seconds();
let c = (-1).seconds();
let d = 1.minutes();
let e = (-1).minutes();
ben.iter(|| a > b);
ben.iter(|| a > c);
ben.iter(|| c < a);
ben.iter(|| b > c);
ben.iter(|| b < a);
ben.iter(|| c < b);
ben.iter(|| d > a);
ben.iter(|| e < c);
}
}
+190
View File
@@ -0,0 +1,190 @@
use std::io;
use criterion::Bencher;
use time::format_description;
use time::format_description::well_known::{Rfc2822, Rfc3339};
use time::macros::{date, datetime, format_description as fd, offset, time};
setup_benchmark! {
"Formatting",
fn format_rfc3339(ben: &mut Bencher<'_>) {
macro_rules! item {
($value:expr) => {
$value.format_into(&mut io::sink(), &Rfc3339)
}
}
ben.iter(|| item!(datetime!(2021-01-02 03:04:05 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.1 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.12 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_4 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_45 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_7 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_78 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 -01:02)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 +01:02)));
}
fn format_rfc2822(ben: &mut Bencher<'_>) {
macro_rules! item {
($value:expr) => {
$value.format_into(&mut io::sink(), &Rfc2822)
}
}
ben.iter(|| item!(datetime!(2021-01-02 03:04:05 UTC)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05 +06:07)));
ben.iter(|| item!(datetime!(2021-01-02 03:04:05 -06:07)));
}
fn format_time(ben: &mut Bencher<'_>) {
macro_rules! item {
($format:expr) => {
time!(13:02:03.456_789_012).format_into(
&mut io::sink(),
&$format,
)
}
}
ben.iter(|| item!(fd!("[hour]")));
ben.iter(|| item!(fd!("[hour repr:12]")));
ben.iter(|| item!(fd!("[hour repr:12 padding:none]")));
ben.iter(|| item!(fd!("[hour repr:12 padding:space]")));
ben.iter(|| item!(fd!("[hour repr:24]")));
ben.iter(|| item!(fd!("[hour repr:24]")));
ben.iter(|| item!(fd!("[hour repr:24 padding:none]")));
ben.iter(|| item!(fd!("[hour repr:24 padding:space]")));
ben.iter(|| item!(fd!("[minute]")));
ben.iter(|| item!(fd!("[minute padding:none]")));
ben.iter(|| item!(fd!("[minute padding:space]")));
ben.iter(|| item!(fd!("[minute padding:zero]")));
ben.iter(|| item!(fd!("[period]")));
ben.iter(|| item!(fd!("[period case:upper]")));
ben.iter(|| item!(fd!("[period case:lower]")));
ben.iter(|| item!(fd!("[second]")));
ben.iter(|| item!(fd!("[second padding:none]")));
ben.iter(|| item!(fd!("[second padding:space]")));
ben.iter(|| item!(fd!("[second padding:zero]")));
ben.iter(|| item!(fd!("[subsecond]")));
ben.iter(|| item!(fd!("[subsecond digits:1]")));
ben.iter(|| item!(fd!("[subsecond digits:2]")));
ben.iter(|| item!(fd!("[subsecond digits:3]")));
ben.iter(|| item!(fd!("[subsecond digits:4]")));
ben.iter(|| item!(fd!("[subsecond digits:5]")));
ben.iter(|| item!(fd!("[subsecond digits:6]")));
ben.iter(|| item!(fd!("[subsecond digits:7]")));
ben.iter(|| item!(fd!("[subsecond digits:8]")));
ben.iter(|| item!(fd!("[subsecond digits:9]")));
ben.iter(|| item!(fd!("[subsecond digits:1+]")));
}
fn display_time(ben: &mut Bencher<'_>) {
ben.iter(|| time!(0:00).to_string());
ben.iter(|| time!(23:59).to_string());
ben.iter(|| time!(23:59:59).to_string());
ben.iter(|| time!(0:00:01).to_string());
ben.iter(|| time!(0:00:00.001).to_string());
ben.iter(|| time!(0:00:00.000_001).to_string());
ben.iter(|| time!(0:00:00.000_000_001).to_string());
}
fn format_date(ben: &mut Bencher<'_>) {
macro_rules! item {
($format:expr) => {
date!(2019-12-31).format_into(&mut io::sink(), &$format)
}
}
ben.iter(|| item!(fd!("[day]")));
ben.iter(|| item!(fd!("[month]")));
ben.iter(|| item!(fd!("[month repr:short]")));
ben.iter(|| item!(fd!("[month repr:long]")));
ben.iter(|| item!(fd!("[ordinal]")));
ben.iter(|| item!(fd!("[weekday]")));
ben.iter(|| item!(fd!("[weekday repr:short]")));
ben.iter(|| item!(fd!("[weekday repr:sunday]")));
ben.iter(|| item!(fd!("[weekday repr:sunday one_indexed:false]")));
ben.iter(|| item!(fd!("[weekday repr:monday]")));
ben.iter(|| item!(fd!("[weekday repr:monday one_indexed:false]")));
ben.iter(|| item!(fd!("[week_number]")));
ben.iter(|| item!(fd!("[week_number padding:none]")));
ben.iter(|| item!(fd!("[week_number padding:space]")));
ben.iter(|| item!(fd!("[week_number repr:sunday]")));
ben.iter(|| item!(fd!("[week_number repr:monday]")));
ben.iter(|| item!(fd!("[year]")));
ben.iter(|| item!(fd!("[year base:iso_week]")));
ben.iter(|| item!(fd!("[year sign:mandatory]")));
ben.iter(|| item!(fd!("[year base:iso_week sign:mandatory]")));
ben.iter(|| item!(fd!("[year repr:last_two]")));
ben.iter(|| item!(fd!("[year base:iso_week repr:last_two]")));
}
fn display_date(ben: &mut Bencher<'_>) {
ben.iter(|| date!(2019-01-01).to_string());
ben.iter(|| date!(2019-12-31).to_string());
ben.iter(|| date!(-4713-11-24).to_string());
ben.iter(|| date!(-0001-01-01).to_string());
}
fn format_offset(ben: &mut Bencher<'_>) {
macro_rules! item {
($value:expr, $format:expr) => {
$value.format_into(&mut io::sink(), &$format)
}
}
ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_hour sign:automatic]")));
ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_hour sign:mandatory]")));
ben.iter(|| item!(offset!(-01:02:03), fd!("[offset_hour sign:automatic]")));
ben.iter(|| item!(offset!(-01:02:03), fd!("[offset_hour sign:mandatory]")));
ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_minute]")));
ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_second]")));
}
fn display_offset(ben: &mut Bencher<'_>) {
ben.iter(|| offset!(UTC).to_string());
ben.iter(|| offset!(+0:00:01).to_string());
ben.iter(|| offset!(-0:00:01).to_string());
ben.iter(|| offset!(+1).to_string());
ben.iter(|| offset!(-1).to_string());
ben.iter(|| offset!(+23:59).to_string());
ben.iter(|| offset!(-23:59).to_string());
ben.iter(|| offset!(+23:59:59).to_string());
ben.iter(|| offset!(-23:59:59).to_string());
}
fn format_pdt(ben: &mut Bencher<'_>) {
ben.iter(|| {
datetime!(1970-01-01 0:00).format_into(
&mut io::sink(),
fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"),
)
});
}
fn display_pdt(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(1970-01-01 0:00).to_string());
ben.iter(|| datetime!(1970-01-01 0:00:01).to_string());
}
fn format_odt(ben: &mut Bencher<'_>) {
let format_description = format_description::parse_borrowed::<1>(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond] [offset_hour \
sign:mandatory]:[offset_minute]:[offset_second]",
).expect("invalid format description");
ben.iter(|| {
datetime!(1970-01-01 0:00 UTC).format_into(&mut io::sink(), &format_description)
});
}
fn display_odt(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(1970-01-01 0:00 UTC).to_string());
}
}
+89
View File
@@ -0,0 +1,89 @@
#![expect(deprecated)]
use std::time::Instant as StdInstant;
use criterion::Bencher;
use time::ext::NumericalDuration;
use time::{Instant, SignedDuration};
setup_benchmark! {
"Instant",
fn checked_add(ben: &mut Bencher<'_>) {
let instant = Instant::now();
let dt = 5.seconds();
ben.iter(|| instant.checked_add(dt));
}
fn checked_sub(ben: &mut Bencher<'_>) {
let instant = Instant::now();
let dt = 5.seconds();
ben.iter(|| instant.checked_sub(dt));
}
fn sub(ben: &mut Bencher<'_>) {
let start: Instant = Instant::now();
let end: Instant = start + 1.milliseconds();
ben.iter(|| end - start);
}
fn add_duration(ben: &mut Bencher<'_>) {
let start = Instant::now();
let dt: SignedDuration = 1.seconds();
ben.iter(|| start + dt);
}
fn std_add_duration(ben: &mut Bencher<'_>) {
let start = StdInstant::now();
let dt: SignedDuration = 1.milliseconds();
ben.iter(|| start + dt);
}
fn add_assign_duration(ben: &mut Bencher<'_>) {
let dt: SignedDuration = 1.milliseconds();
iter_batched_ref!(
ben,
Instant::now,
[|start| *start += dt]
);
}
fn std_add_assign_duration(ben: &mut Bencher<'_>) {
let dt: SignedDuration = 1.milliseconds();
iter_batched_ref!(
ben,
StdInstant::now,
[|start| *start += dt]
);
}
fn sub_duration(ben: &mut Bencher<'_>) {
let instant = Instant::now();
let dt: SignedDuration = 100.milliseconds();
ben.iter(|| instant - dt);
}
fn std_sub_duration(ben: &mut Bencher<'_>) {
let instant = StdInstant::now();
let dt: SignedDuration = 100.milliseconds();
ben.iter(|| instant - dt);
}
fn sub_assign_duration(ben: &mut Bencher<'_>) {
let dt: SignedDuration = 100.milliseconds();
iter_batched_ref!(
ben,
Instant::now,
[|instant| *instant -= dt]
);
}
fn std_sub_assign_duration(ben: &mut Bencher<'_>) {
let dt: SignedDuration = 100.milliseconds();
iter_batched_ref!(
ben,
StdInstant::now,
[|instant| *instant -= dt]
);
}
}
+119
View File
@@ -0,0 +1,119 @@
//! Benchmarks for `time`.
//!
//! These benchmarks are not very precise, but they're good enough to catch major performance
//! regressions. Run them if you think that may be the case. CI **does not** run benchmarks.
#![allow(
clippy::std_instead_of_core,
clippy::std_instead_of_alloc,
clippy::alloc_instead_of_core,
reason = "irrelevant for benchmarks"
)]
#![allow(
clippy::missing_docs_in_private_items,
reason = "may be removed in the future"
)]
#[cfg(not(all(
feature = "default",
feature = "alloc",
feature = "formatting",
feature = "large-dates",
feature = "local-offset",
feature = "macros",
feature = "parsing",
feature = "quickcheck",
feature = "serde-human-readable",
feature = "serde-well-known",
feature = "std",
feature = "rand",
feature = "serde",
bench,
)))]
compile_error!(
"benchmarks must be run as `RUSTFLAGS=\"--cfg bench\" cargo criterion --all-features`"
);
macro_rules! setup_benchmark {
(
$group_prefix:literal,
$(
$(#[$fn_attr:meta])*
fn $fn_name:ident ($bencher:ident : $bencher_type:ty)
$code:block
)*
) => {
$(
$(#[$fn_attr])*
fn $fn_name(
c: &mut ::criterion::Criterion
) {
c.bench_function(
concat!($group_prefix, ": ", stringify!($fn_name)),
|$bencher: $bencher_type| $code
);
}
)*
::criterion::criterion_group! {
name = benches;
config = ::criterion::Criterion::default()
// Set a stricter statistical significance threshold ("p-value")
// for deciding what's an actual performance change vs. noise.
// The more benchmarks, the lower this needs to be in order to
// not get lots of false positives.
.significance_level(0.0001)
// Ignore any performance change less than this (0.05 = 5%) as
// noise, regardless of statistical significance.
.noise_threshold(0.05)
// Reduce the time taken to run each benchmark
.warm_up_time(::std::time::Duration::from_millis(100))
.measurement_time(::std::time::Duration::from_millis(500));
targets = $($fn_name,)*
}
};
}
macro_rules! iter_batched_ref {
($ben:ident, $initializer:expr,[$($routine:expr),+ $(,)?]) => {$(
$ben.iter_batched_ref(
$initializer,
$routine,
::criterion::BatchSize::SmallInput,
);
)+};
}
macro_rules! mods {
($(mod $mod:ident;)+) => {
$(mod $mod;)+
::criterion::criterion_main!($($mod::benches),+);
}
}
mods![
mod date;
mod duration;
mod formatting;
mod instant;
mod month;
mod offset_date_time;
mod parse_format_description;
mod parsing;
mod plain_date_time;
mod rand08;
mod rand09;
mod time;
mod utc_offset;
mod util;
mod weekday;
];
/// Shuffle a slice in a random but deterministic manner.
fn shuffle<T, const N: usize>(mut slice: [T; N]) -> [T; N] {
use ::rand09::prelude::*;
let mut seed = SmallRng::seed_from_u64(0);
slice.shuffle(&mut seed);
slice
}
+66
View File
@@ -0,0 +1,66 @@
use criterion::Bencher;
use time::Month::*;
setup_benchmark! {
"Month",
fn previous(ben: &mut Bencher<'_>) {
ben.iter(|| January.previous());
ben.iter(|| February.previous());
ben.iter(|| March.previous());
ben.iter(|| April.previous());
ben.iter(|| May.previous());
ben.iter(|| June.previous());
ben.iter(|| July.previous());
ben.iter(|| August.previous());
ben.iter(|| September.previous());
ben.iter(|| October.previous());
ben.iter(|| November.previous());
ben.iter(|| December.previous());
}
fn next(ben: &mut Bencher<'_>) {
ben.iter(|| January.next());
ben.iter(|| February.next());
ben.iter(|| March.next());
ben.iter(|| April.next());
ben.iter(|| May.next());
ben.iter(|| June.next());
ben.iter(|| July.next());
ben.iter(|| August.next());
ben.iter(|| September.next());
ben.iter(|| October.next());
ben.iter(|| November.next());
ben.iter(|| December.next());
}
fn length(ben: &mut Bencher<'_>) {
// Common year
ben.iter(|| January.length(2019));
ben.iter(|| February.length(2019));
ben.iter(|| March.length(2019));
ben.iter(|| April.length(2019));
ben.iter(|| May.length(2019));
ben.iter(|| June.length(2019));
ben.iter(|| July.length(2019));
ben.iter(|| August.length(2019));
ben.iter(|| September.length(2019));
ben.iter(|| October.length(2019));
ben.iter(|| November.length(2019));
ben.iter(|| December.length(2019));
// Leap year
ben.iter(|| January.length(2020));
ben.iter(|| February.length(2020));
ben.iter(|| March.length(2020));
ben.iter(|| April.length(2020));
ben.iter(|| May.length(2020));
ben.iter(|| June.length(2020));
ben.iter(|| July.length(2020));
ben.iter(|| August.length(2020));
ben.iter(|| September.length(2020));
ben.iter(|| October.length(2020));
ben.iter(|| November.length(2020));
ben.iter(|| December.length(2020));
}
}
+424
View File
@@ -0,0 +1,424 @@
use std::hint::black_box;
use std::time::SystemTime;
use criterion::Bencher;
use time::OffsetDateTime;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::{date, datetime, offset, time};
setup_benchmark! {
"OffsetDateTime",
fn now_utc(ben: &mut Bencher<'_>) {
ben.iter(OffsetDateTime::now_utc);
}
fn now_local(ben: &mut Bencher<'_>) {
ben.iter(OffsetDateTime::now_local);
}
fn to_offset(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2000-01-01 0:00 +11).to_offset(offset!(-5)));
ben.iter(|| datetime!(2000-01-01 0:00 +11).to_offset(offset!(-8)));
}
fn to_utc(ben: &mut Bencher<'_>) {
ben.iter(|| black_box(datetime!(2000-01-01 0:00 +11).to_utc()));
}
fn from_unix_timestamp(ben: &mut Bencher<'_>) {
ben.iter(|| OffsetDateTime::from_unix_timestamp(0));
ben.iter(|| OffsetDateTime::from_unix_timestamp(1_546_300_800));
}
fn from_unix_timestamp_nanos(ben: &mut Bencher<'_>) {
ben.iter(|| OffsetDateTime::from_unix_timestamp_nanos(0));
ben.iter(|| OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000));
}
fn offset(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).offset());
ben.iter(|| datetime!(2019-01-01 0:00 +1).offset());
ben.iter(|| datetime!(2019-01-01 1:00 +1).offset());
}
fn unix_timestamp(ben: &mut Bencher<'_>) {
ben.iter(|| OffsetDateTime::UNIX_EPOCH.unix_timestamp());
ben.iter(|| datetime!(1970-01-01 1:00 +1).unix_timestamp());
ben.iter(|| datetime!(1970-01-01 0:00 -1).unix_timestamp());
}
fn unix_timestamp_nanos(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos());
ben.iter(|| datetime!(1970-01-01 1:00 +1).unix_timestamp_nanos());
ben.iter(|| datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos());
}
fn date(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).date());
ben.iter(|| datetime!(2018-12-31 23:00 -1).date());
}
fn time(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).time());
ben.iter(|| datetime!(2018-12-31 23:00 -1).time());
}
fn year(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).year());
ben.iter(|| datetime!(2018-12-31 23:00 -1).year());
}
fn ordinal(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).ordinal());
ben.iter(|| datetime!(2018-12-31 23:00 -1).ordinal());
}
fn hour(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).hour());
ben.iter(|| datetime!(2018-12-31 23:00 -1).hour());
}
fn minute(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).minute());
ben.iter(|| datetime!(2018-12-31 23:00 -1).minute());
}
fn second(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC).second());
ben.iter(|| datetime!(2018-12-31 23:00 -1).second());
}
fn replace_time(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00)));
ben.iter(|| datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00)));
ben.iter(|| datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00)));
}
fn replace_date(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30)));
ben.iter(|| datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30)));
}
fn replace_date_time(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00)));
ben.iter(|| datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00)));
}
fn replace_offset(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5)));
}
fn partial_eq(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(1999-12-31 23:00 -1) == datetime!(2000-01-01 0:00 UTC));
}
fn partial_ord(ben: &mut Bencher<'_>) {
ben.iter(||
datetime!(2019-01-01 0:00 UTC).partial_cmp(&datetime!(1999-12-31 23:00 -1))
);
}
fn ord(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00 UTC) == datetime!(2018-12-31 23:00 -1));
ben.iter(|| datetime!(2019-01-01 0:00:00.000_000_001 UTC) > datetime!(2019-01-01 0:00 UTC));
}
fn hash(ben: &mut Bencher<'_>) {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
iter_batched_ref!(
ben,
DefaultHasher::new,
[
|hasher| datetime!(2019-01-01 0:00 UTC).hash(hasher),
|hasher| datetime!(2018-12-31 23:00 -1).hash(hasher),
]
);
}
fn add_duration(ben: &mut Bencher<'_>) {
let a = 5.days();
let b = 1.days();
let c = 2.seconds();
let d = (-2).seconds();
let e = 1.hours();
ben.iter(|| datetime!(2019-01-01 0:00 UTC) + a);
ben.iter(|| datetime!(2019-12-31 0:00 UTC) + b);
ben.iter(|| datetime!(2019-12-31 23:59:59 UTC) + c);
ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) + d);
ben.iter(|| datetime!(1999-12-31 23:00 UTC) + e);
}
fn add_std_duration(ben: &mut Bencher<'_>) {
let a = 5.std_days();
let b = 1.std_days();
let c = 2.std_seconds();
ben.iter(|| datetime!(2019-01-01 0:00 UTC) + a);
ben.iter(|| datetime!(2019-12-31 0:00 UTC) + b);
ben.iter(|| datetime!(2019-12-31 23:59:59 UTC) + c);
}
fn add_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00 UTC),
[
|datetime| *datetime += a,
|datetime| *datetime += b,
]
);
}
fn add_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_days();
let b = 1.std_seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00 UTC),
[
|datetime| *datetime += a,
|datetime| *datetime += b,
]
);
}
fn sub_duration(ben: &mut Bencher<'_>) {
let a = 5.days();
let b = 1.days();
let c = 2.seconds();
ben.iter(|| datetime!(2019-01-06 0:00 UTC) - a);
ben.iter(|| datetime!(2020-01-01 0:00 UTC) - b);
ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) - c);
}
fn sub_std_duration(ben: &mut Bencher<'_>) {
let a = 5.std_days();
let b = 1.std_days();
let c = 2.std_seconds();
ben.iter(|| datetime!(2019-01-06 0:00 UTC) - a);
ben.iter(|| datetime!(2020-01-01 0:00 UTC) - b);
ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) - c);
}
fn sub_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00 UTC),
[
|datetime| *datetime -= a,
|datetime| *datetime -= b,
]
);
}
fn sub_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_days();
let b = 1.std_seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00 UTC),
[
|datetime| *datetime -= a,
|datetime| *datetime -= b,
]
);
}
fn std_add_duration(ben: &mut Bencher<'_>) {
let a1 = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let a2 = 0.seconds();
let b1 = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let b2 = 5.days();
let c1 = SystemTime::from(datetime!(2019-12-31 0:00 UTC));
let c2 = 1.days();
let d1 = SystemTime::from(datetime!(2019-12-31 23:59:59 UTC));
let d2 = 2.seconds();
let e1 = SystemTime::from(datetime!(2020-01-01 0:00:01 UTC));
let e2 = (-2).seconds();
ben.iter(|| a1 + a2);
ben.iter(|| b1 + b2);
ben.iter(|| c1 + c2);
ben.iter(|| d1 + d2);
ben.iter(|| e1 + e2);
}
fn std_add_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| SystemTime::from(datetime!(2019-01-01 0:00 UTC)),
[
|datetime| *datetime += a,
|datetime| *datetime += b,
]
);
}
fn std_sub_duration(ben: &mut Bencher<'_>) {
let a1 = SystemTime::from(datetime!(2019-01-06 0:00 UTC));
let a2 = 5.days();
let b1 = SystemTime::from(datetime!(2020-01-01 0:00 UTC));
let b2 = 1.days();
let c1 = SystemTime::from(datetime!(2020-01-01 0:00:01 UTC));
let c2 = 2.seconds();
let d1 = SystemTime::from(datetime!(2019-12-31 23:59:59 UTC));
let d2 = (-2).seconds();
ben.iter(|| a1 - a2);
ben.iter(|| b1 - b2);
ben.iter(|| c1 - c2);
ben.iter(|| d1 - d2);
}
fn std_sub_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| SystemTime::from(datetime!(2019-01-01 0:00 UTC)),
[
|datetime| *datetime -= a,
|datetime| *datetime -= b,
]
);
}
fn sub_self(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-02 0:00 UTC) - datetime!(2019-01-01 0:00 UTC));
ben.iter(|| datetime!(2019-01-01 0:00 UTC) - datetime!(2019-01-02 0:00 UTC));
ben.iter(|| datetime!(2020-01-01 0:00 UTC) - datetime!(2019-12-31 0:00 UTC));
ben.iter(|| datetime!(2019-12-31 0:00 UTC) - datetime!(2020-01-01 0:00 UTC));
}
fn std_sub(ben: &mut Bencher<'_>) {
let a = SystemTime::from(datetime!(2019-01-02 0:00 UTC));
let b = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let c = SystemTime::from(datetime!(2020-01-01 0:00 UTC));
let d = SystemTime::from(datetime!(2019-12-31 0:00 UTC));
ben.iter(|| a - datetime!(2019-01-01 0:00 UTC));
ben.iter(|| b - datetime!(2019-01-02 0:00 UTC));
ben.iter(|| c - datetime!(2019-12-31 0:00 UTC));
ben.iter(|| d - datetime!(2020-01-01 0:00 UTC));
}
fn sub_std(ben: &mut Bencher<'_>) {
let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let b = SystemTime::from(datetime!(2019-01-02 0:00 UTC));
let c = SystemTime::from(datetime!(2019-12-31 0:00 UTC));
let d = SystemTime::from(datetime!(2020-01-01 0:00 UTC));
ben.iter(|| datetime!(2019-01-02 0:00 UTC) - a);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) - b);
ben.iter(|| datetime!(2020-01-01 0:00 UTC) - c);
ben.iter(|| datetime!(2019-12-31 0:00 UTC) - d);
}
fn eq_std(ben: &mut Bencher<'_>) {
let a = OffsetDateTime::now_utc();
let b = SystemTime::from(a);
ben.iter(|| a == b);
}
fn std_eq(ben: &mut Bencher<'_>) {
let a = OffsetDateTime::now_utc();
let b = SystemTime::from(a);
ben.iter(|| b == a);
}
fn ord_std(ben: &mut Bencher<'_>) {
let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let b = SystemTime::from(datetime!(2020-01-01 0:00 UTC));
let c = SystemTime::from(datetime!(2019-02-01 0:00 UTC));
let d = SystemTime::from(datetime!(2019-01-02 0:00 UTC));
let e = SystemTime::from(datetime!(2019-01-01 1:00:00 UTC));
let f = SystemTime::from(datetime!(2019-01-01 0:01:00 UTC));
let g = SystemTime::from(datetime!(2019-01-01 0:00:01 UTC));
let h = SystemTime::from(datetime!(2019-01-01 0:00:00.001 UTC));
let i = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let j = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let k = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let l = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let m = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let n = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let o = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
ben.iter(|| datetime!(2019-01-01 0:00 UTC) == a);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < b);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < c);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < d);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < e);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < f);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < g);
ben.iter(|| datetime!(2019-01-01 0:00 UTC) < h);
ben.iter(|| datetime!(2020-01-01 0:00 UTC) > i);
ben.iter(|| datetime!(2019-02-01 0:00 UTC) > j);
ben.iter(|| datetime!(2019-01-02 0:00 UTC) > k);
ben.iter(|| datetime!(2019-01-01 1:00:00 UTC) > l);
ben.iter(|| datetime!(2019-01-01 0:01:00 UTC) > m);
ben.iter(|| datetime!(2019-01-01 0:00:01 UTC) > n);
ben.iter(|| datetime!(2019-01-01 0:00:00.000_000_001 UTC) > o);
}
fn std_ord(ben: &mut Bencher<'_>) {
let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let b = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let c = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let d = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let e = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let f = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let g = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let h = SystemTime::from(datetime!(2019-01-01 0:00 UTC));
let i = SystemTime::from(datetime!(2020-01-01 0:00 UTC));
let j = SystemTime::from(datetime!(2019-02-01 0:00 UTC));
let k = SystemTime::from(datetime!(2019-01-02 0:00 UTC));
let l = SystemTime::from(datetime!(2019-01-01 1:00:00 UTC));
let m = SystemTime::from(datetime!(2019-01-01 0:01:00 UTC));
let n = SystemTime::from(datetime!(2019-01-01 0:00:01 UTC));
let o = SystemTime::from(datetime!(2019-01-01 0:00:00.001 UTC));
ben.iter(|| a == datetime!(2019-01-01 0:00 UTC));
ben.iter(|| b < datetime!(2020-01-01 0:00 UTC));
ben.iter(|| c < datetime!(2019-02-01 0:00 UTC));
ben.iter(|| d < datetime!(2019-01-02 0:00 UTC));
ben.iter(|| e < datetime!(2019-01-01 1:00:00 UTC));
ben.iter(|| f < datetime!(2019-01-01 0:01:00 UTC));
ben.iter(|| g < datetime!(2019-01-01 0:00:01 UTC));
ben.iter(|| h < datetime!(2019-01-01 0:00:00.000_000_001 UTC));
ben.iter(|| i > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| j > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| k > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| l > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| m > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| n > datetime!(2019-01-01 0:00 UTC));
ben.iter(|| o > datetime!(2019-01-01 0:00 UTC));
}
fn from_std(ben: &mut Bencher<'_>) {
let a = SystemTime::UNIX_EPOCH;
let b = SystemTime::UNIX_EPOCH - 1.std_days();
let c = SystemTime::UNIX_EPOCH + 1.std_days();
ben.iter(|| OffsetDateTime::from(a));
ben.iter(|| OffsetDateTime::from(b));
ben.iter(|| OffsetDateTime::from(c));
}
fn to_std(ben: &mut Bencher<'_>) {
let a = OffsetDateTime::UNIX_EPOCH;
let b = OffsetDateTime::UNIX_EPOCH + 1.days();
let c = OffsetDateTime::UNIX_EPOCH - 1.days();
ben.iter(|| SystemTime::from(a));
ben.iter(|| SystemTime::from(b));
ben.iter(|| SystemTime::from(c));
}
}
@@ -0,0 +1,27 @@
use std::hint::black_box;
use criterion::Bencher;
use time::format_description;
const FORMAT_DESCRIPTION: &str =
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]";
setup_benchmark! {
"Parse format description",
fn parse_borrowed_v2(ben: &mut Bencher<'_>) {
ben.iter(|| format_description::parse_borrowed::<2>(black_box(FORMAT_DESCRIPTION)));
}
fn parse_borrowed_v3(ben: &mut Bencher<'_>) {
ben.iter(|| format_description::parse_borrowed::<3>(black_box(FORMAT_DESCRIPTION)));
}
fn parse_owned_v2(ben: &mut Bencher<'_>) {
ben.iter(|| format_description::parse_owned::<2>(black_box(FORMAT_DESCRIPTION)));
}
fn parse_owned_v3(ben: &mut Bencher<'_>) {
ben.iter(|| format_description::parse_owned::<3>(black_box(FORMAT_DESCRIPTION)));
}
}
+216
View File
@@ -0,0 +1,216 @@
use criterion::Bencher;
use time::OffsetDateTime;
use time::format_description::well_known::{Rfc2822, Rfc3339};
use time::format_description::{Component, modifier};
use time::parsing::Parsed;
macro_rules! component {
($name:ident {$($field:ident : $value:expr),* $(,)? }) => {{
const COMPONENT: Component = Component::$name({
#[allow(unused_mut, reason = "macro-generated code")]
let mut modifier = modifier::$name::default();
$(modifier.$field = $value;)*
modifier
});
COMPONENT
}};
}
setup_benchmark! {
"Parsing",
fn parse_component_year(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(b"2021", Component::CalendarYearFullStandardRange(
modifier::CalendarYearFullStandardRange::default()
.with_padding(modifier::Padding::Zero)
.with_sign_is_mandatory(false)
))
});
ben.iter(|| {
parsed.parse_component(b"21", Component::CalendarYearLastTwo(
modifier::CalendarYearLastTwo::default().with_padding(modifier::Padding::Zero)
))
});
ben.iter(|| {
parsed.parse_component(b"2021", Component::IsoYearFullStandardRange(
modifier::IsoYearFullStandardRange::default()
.with_padding(modifier::Padding::Zero)
.with_sign_is_mandatory(false)
))
});
ben.iter(|| {
parsed.parse_component(b"21", Component::IsoYearLastTwo(
modifier::IsoYearLastTwo::default().with_padding(modifier::Padding::Zero)
))
});
}
fn parse_component_month(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(b" 1", Component::MonthNumerical(
modifier::MonthNumerical::default().with_padding(modifier::Padding::Space)
))
});
ben.iter(|| {
parsed.parse_component(b"Jan", Component::MonthShort(
modifier::MonthShort::default().with_case_sensitive(true)
))
});
ben.iter(|| {
parsed.parse_component(b"January", Component::MonthLong(
modifier::MonthLong::default().with_case_sensitive(true)
))
});
}
fn parse_component_ordinal(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(b"012", component!(Ordinal {
padding: modifier::Padding::Zero,
}))
});
}
fn parse_component_weekday(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(
b"Sun",
Component::WeekdayShort(modifier::WeekdayShort::default())
)
});
ben.iter(|| {
parsed.parse_component(
b"Sunday",
Component::WeekdayLong(modifier::WeekdayLong::default())
)
});
ben.iter(|| {
parsed.parse_component(
b"0",
Component::WeekdaySunday(modifier::WeekdaySunday::default().with_one_indexed(false))
)
});
ben.iter(|| {
parsed.parse_component(
b"1",
Component::WeekdaySunday(modifier::WeekdaySunday::default().with_one_indexed(true))
)
});
ben.iter(|| {
parsed.parse_component(
b"6",
Component::WeekdayMonday(modifier::WeekdayMonday::default().with_one_indexed(false))
)
});
ben.iter(|| {
parsed.parse_component(
b"7",
Component::WeekdayMonday(modifier::WeekdayMonday::default().with_one_indexed(true))
)
});
}
fn parse_component_week_number(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(b"2", component!(WeekNumberSunday {
padding: modifier::Padding::None,
}))
});
ben.iter(|| {
parsed.parse_component(b"2", component!(WeekNumberMonday {
padding: modifier::Padding::None,
}))
});
ben.iter(|| {
parsed.parse_component(b"2", component!(WeekNumberIso {
padding: modifier::Padding::None,
}))
});
}
fn parse_component_subsecond(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| {
parsed.parse_component(b"1", component!(Subsecond {
digits: modifier::SubsecondDigits::One,
}))
});
ben.iter(|| {
parsed.parse_component(b"12", component!(Subsecond {
digits: modifier::SubsecondDigits::Two,
}))
});
ben.iter(|| {
parsed.parse_component(b"123", component!(Subsecond {
digits: modifier::SubsecondDigits::Three,
}))
});
ben.iter(|| {
parsed.parse_component(b"1234", component!(Subsecond {
digits: modifier::SubsecondDigits::Four,
}))
});
ben.iter(|| {
parsed.parse_component(b"12345", component!(Subsecond {
digits: modifier::SubsecondDigits::Five,
}))
});
ben.iter(|| {
parsed.parse_component(b"123456", component!(Subsecond {
digits: modifier::SubsecondDigits::Six,
}))
});
ben.iter(|| {
parsed.parse_component(b"1234567", component!(Subsecond {
digits: modifier::SubsecondDigits::Seven,
}))
});
ben.iter(|| {
parsed.parse_component(b"12345678", component!(Subsecond {
digits: modifier::SubsecondDigits::Eight,
}))
});
ben.iter(|| {
parsed.parse_component(b"123456789", component!(Subsecond {
digits: modifier::SubsecondDigits::Nine,
}))
});
ben.iter(|| {
parsed.parse_component(b"123456789", component!(Subsecond {
digits: modifier::SubsecondDigits::OneOrMore,
}))
});
}
fn parse_component_unix_timestamp(ben: &mut Bencher<'_>) {
let mut parsed = Parsed::new();
ben.iter(|| parsed.parse_component(std::hint::black_box(b"1234567890"), component!(UnixTimestampSecond {})));
}
fn parse_rfc3339(ben: &mut Bencher<'_>) {
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1234Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12345Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1234567Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12345678Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789Z", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789-01:02", &Rfc3339));
ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789+01:02", &Rfc3339));
}
fn parse_rfc2822(ben: &mut Bencher<'_>) {
ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0000", &Rfc2822));
ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822));
ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822));
}
}
+148
View File
@@ -0,0 +1,148 @@
use criterion::Bencher;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::{datetime, offset};
setup_benchmark! {
"PlainDateTime",
// All getters are trivially dispatched to the relevant field, and do not need to be benchmarked
// a second time.
fn assume_offset(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00).assume_offset(offset!(UTC)));
ben.iter(|| datetime!(2019-01-01 0:00).assume_offset(offset!(-1)));
}
fn assume_utc(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00).assume_utc());
}
fn add_duration(ben: &mut Bencher<'_>) {
let a = 5.days();
let b = 1.days();
let c = 2.seconds();
let d = (-2).seconds();
let e = 1.hours();
ben.iter(|| datetime!(2019-01-01 0:00) + a);
ben.iter(|| datetime!(2019-12-31 0:00) + b);
ben.iter(|| datetime!(2019-12-31 23:59:59) + c);
ben.iter(|| datetime!(2020-01-01 0:00:01) + d);
ben.iter(|| datetime!(1999-12-31 23:00) + e);
}
fn add_std_duration(ben: &mut Bencher<'_>) {
let a = 5.std_days();
let b = 1.std_days();
let c = 2.std_seconds();
ben.iter(|| datetime!(2019-01-01 0:00) + a);
ben.iter(|| datetime!(2019-12-31 0:00) + b);
ben.iter(|| datetime!(2019-12-31 23:59:59) + c);
}
fn add_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00),
[
|datetime| *datetime += a,
|datetime| *datetime += b,
]
);
}
fn add_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_days();
let b = 1.std_seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00),
[
|datetime| *datetime += a,
|datetime| *datetime += b,
]
);
}
fn sub_duration(ben: &mut Bencher<'_>) {
let a = 5.days();
let b = 1.days();
let c = 2.seconds();
let d = (-2).seconds();
let e = (-1).hours();
ben.iter(|| datetime!(2019-01-06 0:00) - a);
ben.iter(|| datetime!(2020-01-01 0:00) - b);
ben.iter(|| datetime!(2020-01-01 0:00:01) - c);
ben.iter(|| datetime!(2019-12-31 23:59:59) - d);
ben.iter(|| datetime!(1999-12-31 23:00) - e);
}
fn sub_std_duration(ben: &mut Bencher<'_>) {
let a = 5.std_days();
let b = 1.std_days();
let c = 2.std_seconds();
ben.iter(|| datetime!(2019-01-06 0:00) - a);
ben.iter(|| datetime!(2020-01-01 0:00) - b);
ben.iter(|| datetime!(2020-01-01 0:00:01) - c);
}
fn sub_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.days();
let b = 1.seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00),
[
|datetime| *datetime -= a,
|datetime| *datetime -= b,
]
);
}
fn sub_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_days();
let b = 1.std_seconds();
iter_batched_ref!(
ben,
|| datetime!(2019-01-01 0:00),
[
|datetime| *datetime -= a,
|datetime| *datetime -= b,
]
);
}
fn sub_datetime(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-02 0:00) - datetime!(2019-01-01 0:00));
ben.iter(|| datetime!(2019-01-01 0:00) - datetime!(2019-01-02 0:00));
ben.iter(|| datetime!(2020-01-01 0:00) - datetime!(2019-12-31 0:00));
ben.iter(|| datetime!(2019-12-31 0:00) - datetime!(2020-01-01 0:00));
}
fn ord(ben: &mut Bencher<'_>) {
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2020-01-01 0:00)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-02-01 0:00)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-02 0:00)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 1:00)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:01)));
ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00:01)));
ben.iter(||
datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00:00.000_000_001))
);
ben.iter(|| datetime!(2020-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-02-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-01-02 0:00).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-01-01 1:00).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-01-01 0:01).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(|| datetime!(2019-01-01 0:00:01).partial_cmp(&datetime!(2019-01-01 0:00)));
ben.iter(||
datetime!(2019-01-01 0:00:00.000_000_001).partial_cmp(&datetime!(2019-01-01 0:00))
);
}
}
+30
View File
@@ -0,0 +1,30 @@
use criterion::Bencher;
use rand08::Rng;
use rand08::rngs::mock::StepRng;
use time::{Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, UtcOffset, Weekday};
macro_rules! bench_rand {
($($name:ident : $type:ty),* $(,)?) => {
setup_benchmark! {
"Random",
$(fn $name(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| StepRng::new(0, 1),
[|rng| rng.r#gen::<$type>()]
);
})*
}
}
}
bench_rand![
time: Time,
date: Date,
utc_offset: UtcOffset,
plain_date_time: PlainDateTime,
offset_date_time: OffsetDateTime,
duration: SignedDuration,
weekday: Weekday,
month: Month,
];
+61
View File
@@ -0,0 +1,61 @@
use criterion::Bencher;
use rand09::Rng;
use time::{Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, UtcOffset, Weekday};
macro_rules! bench_rand {
($($name:ident : $type:ty),* $(,)?) => {
setup_benchmark! {
"Random",
$(fn $name(ben: &mut Bencher<'_>) {
iter_batched_ref!(
ben,
|| StepRng::new(0, 1),
[|rng| rng.random::<$type>()]
);
})*
}
}
}
bench_rand![
time: Time,
date: Date,
utc_offset: UtcOffset,
plain_date_time: PlainDateTime,
offset_date_time: OffsetDateTime,
duration: SignedDuration,
weekday: Weekday,
month: Month,
];
// copy of `StepRng` from rand 0.8 to avoid deprecation warnings
#[derive(Debug, Clone)]
struct StepRng {
v: u64,
a: u64,
}
impl StepRng {
const fn new(initial: u64, increment: u64) -> Self {
Self {
v: initial,
a: increment,
}
}
}
impl rand09::RngCore for StepRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
let res = self.v;
self.v = self.v.wrapping_add(self.a);
res
}
fn fill_bytes(&mut self, dst: &mut [u8]) {
rand09::rand_core::impls::fill_bytes_via_next(self, dst)
}
}
+271
View File
@@ -0,0 +1,271 @@
use std::hint::black_box;
use criterion::Bencher;
use time::Time;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::time;
setup_benchmark! {
"Time",
fn from_hms(ben: &mut Bencher<'_>) {
ben.iter(|| Time::from_hms(1, 2, 3));
}
fn from_hms_milli(ben: &mut Bencher<'_>) {
ben.iter(|| Time::from_hms_milli(1, 2, 3, 4));
}
fn from_hms_micro(ben: &mut Bencher<'_>) {
ben.iter(|| Time::from_hms_micro(1, 2, 3, 4));
}
fn from_hms_nano(ben: &mut Bencher<'_>) {
ben.iter(|| Time::from_hms_nano(1, 2, 3, 4));
}
fn as_hms(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.as_hms());
}
fn as_hms_milli(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.as_hms_milli());
}
fn as_hms_micro(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.as_hms_micro());
}
fn as_hms_nano(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.as_hms_nano());
}
fn hour(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.hour());
}
fn minute(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.minute());
}
fn second(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.second());
}
fn millisecond(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.millisecond());
}
fn microsecond(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.microsecond());
}
fn nanosecond(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT.nanosecond());
}
fn add_duration(ben: &mut Bencher<'_>) {
let a = 1.milliseconds();
let b = 1.seconds();
let c = 1.minutes();
let d = 1.hours();
let e = 1.days();
ben.iter(|| Time::MIDNIGHT + a);
ben.iter(|| Time::MIDNIGHT + b);
ben.iter(|| Time::MIDNIGHT + c);
ben.iter(|| Time::MIDNIGHT + d);
ben.iter(|| Time::MIDNIGHT + e);
}
fn add_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.milliseconds();
let b = 1.seconds();
let c = 1.minutes();
let d = 1.hours();
let e = 1.days();
iter_batched_ref!(
ben,
|| Time::MIDNIGHT,
[
|time| *time += a,
|time| *time += b,
|time| *time += c,
|time| *time += d,
|time| *time += e,
]
);
}
fn sub_duration(ben: &mut Bencher<'_>) {
let a = 1.milliseconds();
let b = 1.seconds();
let c = 1.minutes();
let d = 1.hours();
let e = 1.days();
ben.iter(|| Time::MIDNIGHT - a);
ben.iter(|| Time::MIDNIGHT - b);
ben.iter(|| Time::MIDNIGHT - c);
ben.iter(|| Time::MIDNIGHT - d);
ben.iter(|| Time::MIDNIGHT - e);
}
fn sub_assign_duration(ben: &mut Bencher<'_>) {
let a = 1.milliseconds();
let b = 1.seconds();
let c = 1.minutes();
let d = 1.hours();
let e = 1.days();
iter_batched_ref!(
ben,
|| Time::MIDNIGHT,
[
|time| *time -= a,
|time| *time -= b,
|time| *time -= c,
|time| *time -= d,
|time| *time -= e,
]
);
}
fn add_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_milliseconds();
let b = 1.std_seconds();
let c = 1.std_minutes();
let d = 1.std_hours();
let e = 1.std_days();
ben.iter(|| Time::MIDNIGHT + a);
ben.iter(|| Time::MIDNIGHT + b);
ben.iter(|| Time::MIDNIGHT + c);
ben.iter(|| Time::MIDNIGHT + d);
ben.iter(|| Time::MIDNIGHT + e);
}
fn add_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_milliseconds();
let b = 1.std_seconds();
let c = 1.std_minutes();
let d = 1.std_hours();
let e = 1.std_days();
iter_batched_ref!(
ben,
|| Time::MIDNIGHT,
[
|time| *time += a,
|time| *time += b,
|time| *time += c,
|time| *time += d,
|time| *time += e,
]
);
}
fn sub_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_milliseconds();
let b = 1.std_seconds();
let c = 1.std_minutes();
let d = 1.std_hours();
let e = 1.std_days();
ben.iter(|| Time::MIDNIGHT - a);
ben.iter(|| Time::MIDNIGHT - b);
ben.iter(|| Time::MIDNIGHT - c);
ben.iter(|| Time::MIDNIGHT - d);
ben.iter(|| Time::MIDNIGHT - e);
}
fn sub_assign_std_duration(ben: &mut Bencher<'_>) {
let a = 1.std_milliseconds();
let b = 1.std_seconds();
let c = 1.std_minutes();
let d = 1.std_hours();
let e = 1.std_days();
iter_batched_ref!(
ben,
|| Time::MIDNIGHT,
[
|time| *time -= a,
|time| *time -= b,
|time| *time -= c,
|time| *time -= d,
|time| *time -= e,
]
);
}
fn sub_time(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT - time!(0:00:01));
ben.iter(|| time!(1:00) - Time::MIDNIGHT);
ben.iter(|| time!(1:00) - time!(0:00:01));
}
fn ordering(ben: &mut Bencher<'_>) {
ben.iter(|| Time::MIDNIGHT < time!(0:00:00.000_000_001));
ben.iter(|| Time::MIDNIGHT < time!(0:00:01));
ben.iter(|| time!(12:00) > time!(11:00));
ben.iter(|| Time::MIDNIGHT == time!(0:00:00.000_000_001));
}
fn sort_align_8(ben: &mut Bencher<'_>) {
ben.iter_batched_ref(
|| {
#[repr(C,align(8))]
struct Padder {
arr: [Time;4096],
}
let mut res = Padder {
arr: [Time::MIDNIGHT;4096]
};
let mut last = Time::MIDNIGHT;
let mut last_hour = 0;
for t in &mut res.arr {
*t = last;
t.replace_hour(last_hour).expect("failed to replace hour");
last += 997.std_milliseconds();
last_hour = (last_hour + 5) % 24;
}
res.arr.sort_unstable_by_key(|t|
(t.nanosecond(),t.second(),t.minute(),t.hour())
);
res
},
|v| black_box(v).arr.sort_unstable(),
criterion::BatchSize::SmallInput
)
}
fn sort_align_4(ben: &mut Bencher<'_>) {
ben.iter_batched_ref(
|| {
#[repr(C,align(8))]
struct Padder {
pad: u32,
arr: [Time;4096],
}
let mut res = Padder {
pad: 0,
arr: [Time::MIDNIGHT;4096]
};
let mut last = Time::MIDNIGHT;
let mut last_hour = 0;
for t in &mut res.arr {
*t = last;
t.replace_hour(last_hour).expect("failed to replace hour");
last += 997.std_milliseconds();
last_hour = (last_hour + 5) % 24;
}
res.arr.sort_unstable_by_key(|t|
(t.nanosecond(),t.second(),t.minute(),t.hour())
);
res
},
|v| black_box(v).arr.sort_unstable(),
criterion::BatchSize::SmallInput
)
}
fn duration_until(ben: &mut Bencher<'_>) {
let a = black_box(time!(1:02:03.004_005_006));
let b = black_box(time!(4:05:06.007_008_009));
ben.iter(|| black_box(a.duration_until(b)));
}
}
+12
View File
@@ -0,0 +1,12 @@
use std::hint::black_box;
use criterion::Bencher;
use time::macros::{offset, utc_datetime};
setup_benchmark! {
"UtcDateTime",
fn to_offset(ben: &mut Bencher<'_>) {
ben.iter(|| black_box(utc_datetime!(2000-01-01 0:00)).to_offset(black_box(offset!(-5))));
}
}
+58
View File
@@ -0,0 +1,58 @@
use criterion::Bencher;
use time::{OffsetDateTime, UtcOffset};
setup_benchmark! {
"UtcOffset",
fn from_hms(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::from_hms(0, 0, 0));
}
fn from_whole_seconds(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::from_whole_seconds(0));
}
fn as_hms(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.as_hms());
}
fn whole_hours(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.whole_hours());
}
fn whole_minutes(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.whole_minutes());
}
fn minutes_past_hour(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.minutes_past_hour());
}
fn whole_seconds(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.whole_seconds());
}
fn seconds_past_minute(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.seconds_past_minute());
}
fn is_utc(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.is_utc());
}
fn is_positive(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.is_positive());
}
fn is_negative(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::UTC.is_negative());
}
fn local_offset_at(ben: &mut Bencher<'_>) {
ben.iter(|| UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH));
}
fn current_local_offset(ben: &mut Bencher<'_>) {
ben.iter(UtcOffset::current_local_offset);
}
}
+54
View File
@@ -0,0 +1,54 @@
use std::hint::black_box as bb;
use std::sync::LazyLock;
use criterion::Bencher;
use time::util;
/// Generate a representative sample of all years.
fn representative_years() -> [i32; 800] {
static DATES: LazyLock<[i32; 800]> = LazyLock::new(|| {
let mut years = [0; _];
for year in -400..400 {
years[(year + 400) as usize] = year;
}
crate::shuffle(years)
});
*DATES
}
setup_benchmark! {
"Utils",
fn noop(ben: &mut Bencher<'_>) {
ben.iter(|| {
for i in representative_years() {
let _ = bb(i);
}
});
}
fn is_leap_year(ben: &mut Bencher<'_>) {
ben.iter(|| {
for year in representative_years() {
let _ = bb(util::is_leap_year(bb(year)));
}
});
}
fn days_in_year(ben: &mut Bencher<'_>) {
ben.iter(|| {
for year in representative_years() {
let _ = bb(util::days_in_year(bb(year)));
}
});
}
fn weeks_in_year(ben: &mut Bencher<'_>) {
ben.iter(|| {
for year in representative_years() {
let _ = bb(util::weeks_in_year(bb(year)));
}
});
}
}
+81
View File
@@ -0,0 +1,81 @@
use criterion::Bencher;
use time::Weekday::*;
setup_benchmark! {
"Weekday",
fn previous(ben: &mut Bencher<'_>) {
ben.iter(|| Sunday.previous());
ben.iter(|| Monday.previous());
ben.iter(|| Tuesday.previous());
ben.iter(|| Wednesday.previous());
ben.iter(|| Thursday.previous());
ben.iter(|| Friday.previous());
ben.iter(|| Saturday.previous());
}
fn next(ben: &mut Bencher<'_>) {
ben.iter(|| Sunday.next());
ben.iter(|| Monday.next());
ben.iter(|| Tuesday.next());
ben.iter(|| Wednesday.next());
ben.iter(|| Thursday.next());
ben.iter(|| Friday.next());
ben.iter(|| Saturday.next());
}
fn nth(ben: &mut Bencher<'_>) {
ben.iter(|| Sunday.nth_next(0));
ben.iter(|| Sunday.nth_next(1));
ben.iter(|| Sunday.nth_next(2));
ben.iter(|| Sunday.nth_next(3));
ben.iter(|| Sunday.nth_next(4));
ben.iter(|| Sunday.nth_next(5));
ben.iter(|| Sunday.nth_next(6));
ben.iter(|| Sunday.nth_next(7));
ben.iter(|| Sunday.nth_next(u8::MAX));
ben.iter(|| Monday.nth_next(7));
ben.iter(|| Monday.nth_next(u8::MAX));
}
fn number_from_monday(ben: &mut Bencher<'_>) {
ben.iter(|| Monday.number_from_monday());
ben.iter(|| Tuesday.number_from_monday());
ben.iter(|| Wednesday.number_from_monday());
ben.iter(|| Thursday.number_from_monday());
ben.iter(|| Friday.number_from_monday());
ben.iter(|| Saturday.number_from_monday());
ben.iter(|| Sunday.number_from_monday());
}
fn number_from_sunday(ben: &mut Bencher<'_>) {
ben.iter(|| Sunday.number_from_sunday());
ben.iter(|| Monday.number_from_sunday());
ben.iter(|| Tuesday.number_from_sunday());
ben.iter(|| Wednesday.number_from_sunday());
ben.iter(|| Thursday.number_from_sunday());
ben.iter(|| Friday.number_from_sunday());
ben.iter(|| Saturday.number_from_sunday());
}
fn number_days_from_monday(ben: &mut Bencher<'_>) {
ben.iter(|| Monday.number_days_from_monday());
ben.iter(|| Tuesday.number_days_from_monday());
ben.iter(|| Wednesday.number_days_from_monday());
ben.iter(|| Thursday.number_days_from_monday());
ben.iter(|| Friday.number_days_from_monday());
ben.iter(|| Saturday.number_days_from_monday());
ben.iter(|| Sunday.number_days_from_monday());
}
fn number_days_from_sunday(ben: &mut Bencher<'_>) {
ben.iter(|| Sunday.number_days_from_sunday());
ben.iter(|| Monday.number_days_from_sunday());
ben.iter(|| Tuesday.number_days_from_sunday());
ben.iter(|| Wednesday.number_days_from_sunday());
ben.iter(|| Thursday.number_days_from_sunday());
ben.iter(|| Friday.number_days_from_sunday());
ben.iter(|| Saturday.number_days_from_sunday());
}
}
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
//! Component range error
use core::fmt;
use crate::error;
/// An error type indicating that a component provided to a method was out of range, causing a
/// failure.
// i64 is the narrowest type fitting all use cases. This eliminates the need for a type parameter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ComponentRange {
/// Name of the component.
pub(crate) name: &'static str,
/// Whether an input with the same value could have succeeded if the values of other components
/// were different.
pub(crate) is_conditional: bool,
}
impl ComponentRange {
/// Create a new `ComponentRange` error that is not conditional.
#[inline]
pub(crate) const fn unconditional(name: &'static str) -> Self {
Self {
name,
is_conditional: false,
}
}
/// Create a new `ComponentRange` error that is conditional.
#[inline]
pub(crate) const fn conditional(name: &'static str) -> Self {
Self {
name,
is_conditional: true,
}
}
/// Obtain the name of the component whose value was out of range.
#[inline]
pub const fn name(self) -> &'static str {
self.name
}
/// Whether the value's permitted range is conditional, i.e. whether an input with this
/// value could have succeeded if the values of other components were different.
#[inline]
pub const fn is_conditional(self) -> bool {
self.is_conditional
}
}
impl fmt::Display for ComponentRange {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} was not in range", self.name)
}
}
impl From<ComponentRange> for crate::Error {
#[inline]
fn from(original: ComponentRange) -> Self {
Self::ComponentRange(original)
}
}
impl TryFrom<crate::Error> for ComponentRange {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::ComponentRange(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
/// **This trait implementation is deprecated and will be removed in a future breaking release.**
#[cfg(feature = "serde")]
impl serde_core::de::Expected for ComponentRange {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an in-range value")
}
}
#[cfg(feature = "serde")]
impl ComponentRange {
/// Convert the error to a deserialization error.
#[inline]
pub(crate) fn into_de_error<E>(self) -> E
where
E: serde_core::de::Error,
{
serde_core::de::Error::custom(format_args!(
"invalid {}, expected an in-range value",
self.name
))
}
}
impl core::error::Error for ComponentRange {}
+38
View File
@@ -0,0 +1,38 @@
//! Conversion range error
use core::fmt;
use crate::error;
/// An error type indicating that a conversion failed because the target type could not store the
/// initial value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConversionRange;
impl fmt::Display for ConversionRange {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Source value is out of range for the target type")
}
}
impl core::error::Error for ConversionRange {}
impl From<ConversionRange> for crate::Error {
#[inline]
fn from(err: ConversionRange) -> Self {
Self::ConversionRange(err)
}
}
impl TryFrom<crate::Error> for ConversionRange {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::ConversionRange(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
@@ -0,0 +1,36 @@
//! Different variant error
use core::fmt;
/// An error type indicating that a [`TryFrom`](core::convert::TryFrom) call failed because the
/// original value was of a different variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DifferentVariant;
impl fmt::Display for DifferentVariant {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "value was of a different variant than required")
}
}
impl core::error::Error for DifferentVariant {}
impl From<DifferentVariant> for crate::Error {
#[inline]
fn from(err: DifferentVariant) -> Self {
Self::DifferentVariant(err)
}
}
impl TryFrom<crate::Error> for DifferentVariant {
type Error = Self;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::DifferentVariant(err) => Ok(err),
_ => Err(Self),
}
}
}
+124
View File
@@ -0,0 +1,124 @@
//! Error formatting a struct
use alloc::boxed::Box;
use core::fmt;
use std::io;
use crate::error;
/// An error occurred when formatting.
#[non_exhaustive]
#[derive(Debug)]
pub enum Format {
/// The type being formatted does not contain sufficient information to format a component.
#[non_exhaustive]
InsufficientTypeInformation,
/// The component named has a value that cannot be formatted into the requested format.
///
/// This variant is only returned when using well-known formats.
InvalidComponent(&'static str),
/// A component provided was out of range.
ComponentRange(Box<error::ComponentRange>),
/// A value of `std::io::Error` was returned internally.
StdIo(io::Error),
}
impl fmt::Display for Format {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InsufficientTypeInformation => f.write_str(
"The type being formatted does not contain sufficient information to format a \
component.",
),
Self::InvalidComponent(component) => write!(
f,
"The {component} component cannot be formatted into the requested format."
),
Self::ComponentRange(err) => err.fmt(f),
Self::StdIo(err) => err.fmt(f),
}
}
}
impl From<error::ComponentRange> for Format {
#[inline]
fn from(err: error::ComponentRange) -> Self {
Self::ComponentRange(Box::new(err))
}
}
impl From<io::Error> for Format {
#[inline]
fn from(err: io::Error) -> Self {
Self::StdIo(err)
}
}
impl TryFrom<Format> for error::ComponentRange {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: Format) -> Result<Self, Self::Error> {
match err {
Format::ComponentRange(err) => Ok(*err),
_ => Err(error::DifferentVariant),
}
}
}
impl TryFrom<Format> for io::Error {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: Format) -> Result<Self, Self::Error> {
match err {
Format::StdIo(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
impl core::error::Error for Format {
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::InsufficientTypeInformation | Self::InvalidComponent(_) => None,
Self::ComponentRange(err) => Some(&**err),
Self::StdIo(err) => Some(err),
}
}
}
impl From<Format> for crate::Error {
#[inline]
fn from(original: Format) -> Self {
Self::Format(original)
}
}
impl TryFrom<crate::Error> for Format {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::Format(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
#[cfg(feature = "serde")]
impl Format {
/// Obtain an error type for the serializer.
#[doc(hidden)] // Exposed only for the `declare_format_string` macro
#[inline]
pub fn into_invalid_serde_value<S>(self) -> S::Error
where
S: serde_core::Serializer,
{
use serde_core::ser::Error;
S::Error::custom(self)
}
}
@@ -0,0 +1,37 @@
//! Indeterminate offset
use core::fmt;
use crate::error;
/// The system's UTC offset could not be determined at the given datetime.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndeterminateOffset;
impl fmt::Display for IndeterminateOffset {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("The system's UTC offset could not be determined")
}
}
impl core::error::Error for IndeterminateOffset {}
impl From<IndeterminateOffset> for crate::Error {
#[inline]
fn from(err: IndeterminateOffset) -> Self {
Self::IndeterminateOffset(err)
}
}
impl TryFrom<crate::Error> for IndeterminateOffset {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::IndeterminateOffset(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
@@ -0,0 +1,163 @@
//! Invalid format description
use alloc::string::String;
use core::fmt;
use crate::error;
/// The format description provided was not valid.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InvalidFormatDescription {
/// There was a bracket pair that was opened but not closed.
#[non_exhaustive]
UnclosedOpeningBracket {
/// The zero-based index of the opening bracket.
index: usize,
},
/// A component name is not valid.
#[non_exhaustive]
InvalidComponentName {
/// The name of the invalid component name.
name: String,
/// The zero-based index the component name starts at.
index: usize,
},
/// A modifier is not valid.
#[non_exhaustive]
InvalidModifier {
/// The value of the invalid modifier.
value: String,
/// The zero-based index the modifier starts at.
index: usize,
},
/// A component name is missing.
#[non_exhaustive]
MissingComponentName {
/// The zero-based index where the component name should start.
index: usize,
},
/// A required modifier is missing.
#[non_exhaustive]
MissingRequiredModifier {
/// The name of the modifier that is missing.
name: &'static str,
/// The zero-based index of the component.
index: usize,
},
/// Something was expected, but not found.
#[non_exhaustive]
Expected {
/// What was expected to be present, but wasn't.
what: &'static str,
/// The zero-based index the item was expected to be found at.
index: usize,
},
/// Certain behavior is not supported in the given context.
#[non_exhaustive]
NotSupported {
/// The behavior that is not supported.
what: &'static str,
/// The context in which the behavior is not supported.
context: &'static str,
/// The zero-based index the error occurred at.
index: usize,
},
/// A modifier was present more than once.
#[non_exhaustive]
DuplicateModifier {
/// The name of the modifier that is duplicated.
name: &'static str,
/// The zero-based index of the second occurrence of the modifier.
index: usize,
},
/// A combination of modifiers is not valid.
#[non_exhaustive]
InvalidModifierCombination {
/// The modifier that is not valid in combination with the other modifiers.
modifier: &'static str,
/// The context in which the modifier is not valid.
context: &'static str,
/// The zero-based index the error occurred at.
index: usize,
},
}
impl From<InvalidFormatDescription> for crate::Error {
#[inline]
fn from(original: InvalidFormatDescription) -> Self {
Self::InvalidFormatDescription(original)
}
}
impl TryFrom<crate::Error> for InvalidFormatDescription {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::InvalidFormatDescription(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
impl fmt::Display for InvalidFormatDescription {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use InvalidFormatDescription::*;
match self {
UnclosedOpeningBracket { index } => {
write!(f, "unclosed opening bracket at byte index {index}")
}
InvalidComponentName { name, index } => {
write!(f, "invalid component name `{name}` at byte index {index}")
}
InvalidModifier { value, index } => {
write!(f, "invalid modifier `{value}` at byte index {index}")
}
MissingComponentName { index } => {
write!(f, "missing component name at byte index {index}")
}
MissingRequiredModifier { name, index } => {
write!(
f,
"missing required modifier `{name}` for component at byte index {index}"
)
}
Expected {
what: expected,
index,
} => {
write!(f, "expected {expected} at byte index {index}")
}
NotSupported {
what,
context,
index,
} => {
if context.is_empty() {
write!(f, "{what} is not supported at byte index {index}")
} else {
write!(
f,
"{what} is not supported in {context} at byte index {index}"
)
}
}
DuplicateModifier { name, index } => {
write!(f, "duplicate modifier `{name}` at byte index {index}")
}
InvalidModifierCombination {
modifier,
context,
index,
} => write!(
f,
"the `{modifier}` modifier is not valid at byte index {index} {context}"
),
}
}
}
impl core::error::Error for InvalidFormatDescription {}
+36
View File
@@ -0,0 +1,36 @@
//! Invalid variant error
use core::fmt;
/// An error type indicating that a [`FromStr`](core::str::FromStr) call failed because the value
/// was not a valid variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidVariant;
impl fmt::Display for InvalidVariant {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "value was not a valid variant")
}
}
impl core::error::Error for InvalidVariant {}
impl From<InvalidVariant> for crate::Error {
#[inline]
fn from(err: InvalidVariant) -> Self {
Self::InvalidVariant(err)
}
}
impl TryFrom<crate::Error> for InvalidVariant {
type Error = crate::error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::InvalidVariant(err) => Ok(err),
_ => Err(crate::error::DifferentVariant),
}
}
}
+132
View File
@@ -0,0 +1,132 @@
//! Various error types returned by methods in the time crate.
mod component_range;
mod conversion_range;
mod different_variant;
#[cfg(feature = "formatting")]
mod format;
#[cfg(feature = "local-offset")]
mod indeterminate_offset;
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
mod invalid_format_description;
mod invalid_variant;
#[cfg(feature = "parsing")]
mod parse;
#[cfg(feature = "parsing")]
mod parse_from_description;
#[cfg(feature = "parsing")]
mod try_from_parsed;
#[cfg(feature = "parsing")]
use core::convert::Infallible;
use core::fmt;
pub use component_range::ComponentRange;
pub use conversion_range::ConversionRange;
pub use different_variant::DifferentVariant;
#[cfg(feature = "formatting")]
pub use format::Format;
#[cfg(feature = "local-offset")]
pub use indeterminate_offset::IndeterminateOffset;
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
pub use invalid_format_description::InvalidFormatDescription;
pub use invalid_variant::InvalidVariant;
#[cfg(feature = "parsing")]
pub use parse::Parse;
#[cfg(feature = "parsing")]
pub use parse_from_description::ParseFromDescription;
#[cfg(feature = "parsing")]
pub use try_from_parsed::TryFromParsed;
/// 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.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
#[expect(missing_docs)]
ConversionRange(ConversionRange),
#[expect(missing_docs)]
ComponentRange(ComponentRange),
#[cfg(feature = "local-offset")]
#[expect(missing_docs)]
IndeterminateOffset(IndeterminateOffset),
#[cfg(feature = "formatting")]
#[expect(missing_docs)]
Format(Format),
#[cfg(feature = "parsing")]
#[expect(missing_docs)]
ParseFromDescription(ParseFromDescription),
#[cfg(feature = "parsing")]
#[expect(missing_docs)]
#[non_exhaustive]
#[deprecated(
since = "0.3.28",
note = "no longer output. moved to the `ParseFromDescription` variant"
)]
UnexpectedTrailingCharacters {
#[doc(hidden)]
never: Infallible,
},
#[cfg(feature = "parsing")]
#[expect(missing_docs)]
TryFromParsed(TryFromParsed),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
#[expect(missing_docs)]
InvalidFormatDescription(InvalidFormatDescription),
#[expect(missing_docs)]
DifferentVariant(DifferentVariant),
#[expect(missing_docs)]
InvalidVariant(InvalidVariant),
}
impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ConversionRange(e) => e.fmt(f),
Self::ComponentRange(e) => e.fmt(f),
#[cfg(feature = "local-offset")]
Self::IndeterminateOffset(e) => e.fmt(f),
#[cfg(feature = "formatting")]
Self::Format(e) => e.fmt(f),
#[cfg(feature = "parsing")]
Self::ParseFromDescription(e) => e.fmt(f),
#[cfg(feature = "parsing")]
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters { never } => match *never {},
#[cfg(feature = "parsing")]
Self::TryFromParsed(e) => e.fmt(f),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
Self::InvalidFormatDescription(e) => e.fmt(f),
Self::DifferentVariant(e) => e.fmt(f),
Self::InvalidVariant(e) => e.fmt(f),
}
}
}
impl core::error::Error for Error {
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::ConversionRange(err) => Some(err),
Self::ComponentRange(err) => Some(err),
#[cfg(feature = "local-offset")]
Self::IndeterminateOffset(err) => Some(err),
#[cfg(feature = "formatting")]
Self::Format(err) => Some(err),
#[cfg(feature = "parsing")]
Self::ParseFromDescription(err) => Some(err),
#[cfg(feature = "parsing")]
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters { never } => match *never {},
#[cfg(feature = "parsing")]
Self::TryFromParsed(err) => Some(err),
#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
Self::InvalidFormatDescription(err) => Some(err),
Self::DifferentVariant(err) => Some(err),
Self::InvalidVariant(err) => Some(err),
}
}
}
+116
View File
@@ -0,0 +1,116 @@
//! Error that occurred at some stage of parsing
use core::convert::Infallible;
use core::fmt;
use crate::error::{self, ParseFromDescription, TryFromParsed};
/// An error that occurred at some stage of parsing.
#[non_exhaustive]
#[allow(variant_size_differences, reason = "only triggers on some platforms")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Parse {
#[expect(missing_docs)]
TryFromParsed(TryFromParsed),
#[expect(missing_docs)]
ParseFromDescription(ParseFromDescription),
#[expect(missing_docs)]
#[non_exhaustive]
#[deprecated(
since = "0.3.28",
note = "no longer output. moved to the `ParseFromDescription` variant"
)]
UnexpectedTrailingCharacters {
#[doc(hidden)]
never: Infallible,
},
}
impl fmt::Display for Parse {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TryFromParsed(err) => err.fmt(f),
Self::ParseFromDescription(err) => err.fmt(f),
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters { never } => match *never {},
}
}
}
impl core::error::Error for Parse {
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::TryFromParsed(err) => Some(err),
Self::ParseFromDescription(err) => Some(err),
#[allow(deprecated)]
Self::UnexpectedTrailingCharacters { never } => match *never {},
}
}
}
impl From<TryFromParsed> for Parse {
#[inline]
fn from(err: TryFromParsed) -> Self {
Self::TryFromParsed(err)
}
}
impl TryFrom<Parse> for TryFromParsed {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: Parse) -> Result<Self, Self::Error> {
match err {
Parse::TryFromParsed(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
impl From<ParseFromDescription> for Parse {
#[inline]
fn from(err: ParseFromDescription) -> Self {
Self::ParseFromDescription(err)
}
}
impl TryFrom<Parse> for ParseFromDescription {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: Parse) -> Result<Self, Self::Error> {
match err {
Parse::ParseFromDescription(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
impl From<Parse> for crate::Error {
#[inline]
fn from(err: Parse) -> Self {
match err {
Parse::TryFromParsed(err) => Self::TryFromParsed(err),
Parse::ParseFromDescription(err) => Self::ParseFromDescription(err),
#[allow(deprecated)]
Parse::UnexpectedTrailingCharacters { never } => match never {},
}
}
}
impl TryFrom<crate::Error> for Parse {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)),
#[allow(deprecated)]
crate::Error::UnexpectedTrailingCharacters { never } => match never {},
crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)),
_ => Err(error::DifferentVariant),
}
}
}
@@ -0,0 +1,55 @@
//! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct
use core::fmt;
use crate::error;
/// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseFromDescription {
/// A string literal was not what was expected.
#[non_exhaustive]
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 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLiteral => f.write_str("a character literal was not valid"),
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")
}
}
}
}
impl core::error::Error for ParseFromDescription {}
impl From<ParseFromDescription> for crate::Error {
#[inline]
fn from(original: ParseFromDescription) -> Self {
Self::ParseFromDescription(original)
}
}
impl TryFrom<crate::Error> for ParseFromDescription {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::ParseFromDescription(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
+77
View File
@@ -0,0 +1,77 @@
//! Error converting a [`Parsed`](crate::parsing::Parsed) struct to another type
use core::fmt;
use crate::error;
/// An error that occurred when converting a [`Parsed`](crate::parsing::Parsed) to another type.
#[non_exhaustive]
#[allow(variant_size_differences, reason = "only triggers on some platforms")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TryFromParsed {
/// The [`Parsed`](crate::parsing::Parsed) did not include enough information to construct the
/// type.
InsufficientInformation,
/// Some component contained an invalid value for the type.
ComponentRange(error::ComponentRange),
}
impl fmt::Display for TryFromParsed {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InsufficientInformation => f.write_str(
"the `Parsed` struct did not include enough information to construct the type",
),
Self::ComponentRange(err) => err.fmt(f),
}
}
}
impl From<error::ComponentRange> for TryFromParsed {
#[inline]
fn from(v: error::ComponentRange) -> Self {
Self::ComponentRange(v)
}
}
impl TryFrom<TryFromParsed> for error::ComponentRange {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: TryFromParsed) -> Result<Self, Self::Error> {
match err {
TryFromParsed::ComponentRange(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
impl core::error::Error for TryFromParsed {
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::InsufficientInformation => None,
Self::ComponentRange(err) => Some(err),
}
}
}
impl From<TryFromParsed> for crate::Error {
#[inline]
fn from(original: TryFromParsed) -> Self {
Self::TryFromParsed(original)
}
}
impl TryFrom<crate::Error> for TryFromParsed {
type Error = error::DifferentVariant;
#[inline]
fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
match err {
crate::Error::TryFromParsed(err) => Ok(err),
_ => Err(error::DifferentVariant),
}
}
}
+27
View File
@@ -0,0 +1,27 @@
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 {
#[inline]
fn num_digits(self) -> u8 {
match self.checked_ilog10() {
Some(n) => n.truncate::<u8>() + 1,
None => 1,
}
}
})*
};
}
impl_digit_count!(u8, u16, u32);
+105
View File
@@ -0,0 +1,105 @@
use std::time::Instant as StdInstant;
use crate::SignedDuration;
/// 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::SignedDuration`](SignedDuration)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.
#[inline]
#[track_caller]
fn add_signed(self, duration: SignedDuration) -> 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.
#[inline]
#[track_caller]
fn sub_signed(self, duration: SignedDuration) -> 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: SignedDuration) -> 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: SignedDuration) -> 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) -> SignedDuration;
}
impl InstantExt for StdInstant {
#[inline]
fn checked_add_signed(&self, duration: SignedDuration) -> Option<Self> {
if duration.is_positive() {
self.checked_add(duration.unsigned_abs())
} else if duration.is_negative() {
self.checked_sub(duration.unsigned_abs())
} else {
debug_assert!(duration.is_zero());
Some(*self)
}
}
#[inline]
fn checked_sub_signed(&self, duration: SignedDuration) -> Option<Self> {
if duration.is_positive() {
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)
}
}
#[inline]
fn signed_duration_since(&self, earlier: Self) -> SignedDuration {
if *self > earlier {
self.saturating_duration_since(earlier)
.try_into()
.unwrap_or(SignedDuration::MAX)
} else {
earlier
.saturating_duration_since(*self)
.try_into()
.map_or(SignedDuration::MIN, |d: SignedDuration| -d)
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//! Extension traits.
mod digit_count;
#[cfg(feature = "std")]
mod instant;
mod numerical_duration;
mod numerical_std_duration;
#[cfg(feature = "std")]
mod systemtime;
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;
#[cfg(feature = "std")]
pub use self::systemtime::SystemTimeExt;
+166
View File
@@ -0,0 +1,166 @@
use crate::SignedDuration;
use crate::unit::*;
/// 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 [`SignedDuration`]s from numeric literals.
///
/// # Examples
///
/// Basic construction of [`SignedDuration`]s.
///
/// ```rust
/// # use time::{SignedDuration, ext::NumericalDuration};
/// assert_eq!(5.nanoseconds(), SignedDuration::nanoseconds(5));
/// assert_eq!(5.microseconds(), SignedDuration::microseconds(5));
/// assert_eq!(5.milliseconds(), SignedDuration::milliseconds(5));
/// assert_eq!(5.seconds(), SignedDuration::seconds(5));
/// assert_eq!(5.minutes(), SignedDuration::minutes(5));
/// assert_eq!(5.hours(), SignedDuration::hours(5));
/// assert_eq!(5.days(), SignedDuration::days(5));
/// assert_eq!(5.weeks(), SignedDuration::weeks(5));
/// ```
///
/// Signed integers work as well!
///
/// ```rust
/// # use time::{SignedDuration, ext::NumericalDuration};
/// assert_eq!((-5).nanoseconds(), SignedDuration::nanoseconds(-5));
/// assert_eq!((-5).microseconds(), SignedDuration::microseconds(-5));
/// assert_eq!((-5).milliseconds(), SignedDuration::milliseconds(-5));
/// assert_eq!((-5).seconds(), SignedDuration::seconds(-5));
/// assert_eq!((-5).minutes(), SignedDuration::minutes(-5));
/// assert_eq!((-5).hours(), SignedDuration::hours(-5));
/// assert_eq!((-5).days(), SignedDuration::days(-5));
/// assert_eq!((-5).weeks(), SignedDuration::weeks(-5));
/// ```
///
/// Just like any other [`SignedDuration`], 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.
#[diagnostic::on_unimplemented(note = "this extension trait is intended to be used with numeric \
literals, such as `5.seconds()`")]
pub trait NumericalDuration: sealed::Sealed {
/// Create a [`SignedDuration`] from the number of nanoseconds.
fn nanoseconds(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of microseconds.
fn microseconds(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of milliseconds.
fn milliseconds(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of seconds.
fn seconds(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of minutes.
fn minutes(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of hours.
fn hours(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of days.
fn days(self) -> SignedDuration;
/// Create a [`SignedDuration`] from the number of weeks.
fn weeks(self) -> SignedDuration;
}
impl NumericalDuration for i64 {
#[inline]
fn nanoseconds(self) -> SignedDuration {
SignedDuration::nanoseconds(self)
}
#[inline]
fn microseconds(self) -> SignedDuration {
SignedDuration::microseconds(self)
}
#[inline]
fn milliseconds(self) -> SignedDuration {
SignedDuration::milliseconds(self)
}
#[inline]
fn seconds(self) -> SignedDuration {
SignedDuration::seconds(self)
}
#[inline]
#[track_caller]
fn minutes(self) -> SignedDuration {
SignedDuration::minutes(self)
}
#[inline]
#[track_caller]
fn hours(self) -> SignedDuration {
SignedDuration::hours(self)
}
#[inline]
#[track_caller]
fn days(self) -> SignedDuration {
SignedDuration::days(self)
}
#[inline]
#[track_caller]
fn weeks(self) -> SignedDuration {
SignedDuration::weeks(self)
}
}
impl NumericalDuration for f64 {
#[inline]
fn nanoseconds(self) -> SignedDuration {
SignedDuration::nanoseconds(self as i64)
}
#[inline]
fn microseconds(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Microsecond)) as i64)
}
#[inline]
fn milliseconds(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Millisecond)) as i64)
}
#[inline]
fn seconds(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Second)) as i64)
}
#[inline]
#[track_caller]
fn minutes(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Minute)) as i64)
}
#[inline]
#[track_caller]
fn hours(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Hour)) as i64)
}
#[inline]
#[track_caller]
fn days(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Day)) as i64)
}
#[inline]
#[track_caller]
fn weeks(self) -> SignedDuration {
SignedDuration::nanoseconds((self * Nanosecond::per_t::<Self>(Week)) as i64)
}
}
@@ -0,0 +1,220 @@
use core::time::Duration as StdDuration;
use crate::unit::*;
/// 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.
#[diagnostic::on_unimplemented(note = "this extension trait is intended to be used with numeric \
literals, such as `5.std_seconds()`")]
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 {
#[inline]
fn std_nanoseconds(self) -> StdDuration {
StdDuration::from_nanos(self)
}
#[inline]
fn std_microseconds(self) -> StdDuration {
StdDuration::from_micros(self)
}
#[inline]
fn std_milliseconds(self) -> StdDuration {
StdDuration::from_millis(self)
}
#[inline]
fn std_seconds(self) -> StdDuration {
StdDuration::from_secs(self)
}
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn std_minutes(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per_t(Minute))
.expect("overflow constructing `time::SignedDuration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn std_hours(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per_t(Hour))
.expect("overflow constructing `time::SignedDuration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn std_days(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per_t(Day))
.expect("overflow constructing `time::SignedDuration`"),
)
}
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn std_weeks(self) -> StdDuration {
StdDuration::from_secs(
self.checked_mul(Second::per_t(Week))
.expect("overflow constructing `time::SignedDuration`"),
)
}
}
impl NumericalStdDuration for f64 {
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_nanoseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos(self as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_microseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Microsecond)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_milliseconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Millisecond)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_seconds(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Second)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_minutes(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Minute)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_hours(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Hour)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_days(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Day)) as u64)
}
/// # Panics
///
/// This will panic if self is negative.
#[inline]
#[track_caller]
fn std_weeks(self) -> StdDuration {
assert!(self >= 0.);
StdDuration::from_nanos((self * Nanosecond::per_t::<Self>(Week)) as u64)
}
}
+82
View File
@@ -0,0 +1,82 @@
use std::time::SystemTime;
use crate::SignedDuration;
/// 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::SystemTime {}
}
/// An extension trait for [`std::time::SystemTime`] that adds methods for
/// [`time::SignedDuration`](SignedDuration)s.
pub trait SystemTimeExt: sealed::Sealed {
/// Adds the given [`SignedDuration`] to the [`SystemTime`], returning `None` is the result
/// cannot be represented by the underlying data structure.
fn checked_add_signed(&self, duration: SignedDuration) -> Option<Self>;
/// Subtracts the given [`SignedDuration`] from the [`SystemTime`], returning `None` is the
/// result cannot be represented by the underlying data structure.
fn checked_sub_signed(&self, duration: SignedDuration) -> Option<Self>;
/// Returns the amount of time elapsed from another [`SystemTime`] to this one. This will be
/// negative if `earlier` is later than `self.`
///
/// If the duration cannot be stored by [`SignedDuration`], the value will be saturated to
/// [`SignedDuration::MIN`] or [`SignedDuration::MAX`] as appropriate.
///
/// # Example
///
/// ```rust
/// # use std::time::SystemTime;
/// # use time::ext::{NumericalDuration, SystemTimeExt};
/// let epoch = SystemTime::UNIX_EPOCH;
/// let other = epoch + 1.seconds();
/// assert_eq!(other.signed_duration_since(epoch), 1.seconds());
/// assert_eq!(epoch.signed_duration_since(other), (-1).seconds());
/// ```
fn signed_duration_since(&self, earlier: Self) -> SignedDuration;
}
impl SystemTimeExt for SystemTime {
#[inline]
fn checked_add_signed(&self, duration: SignedDuration) -> Option<Self> {
if duration.is_positive() {
self.checked_add(duration.unsigned_abs())
} else if duration.is_negative() {
self.checked_sub(duration.unsigned_abs())
} else {
Some(*self)
}
}
#[inline]
fn checked_sub_signed(&self, duration: SignedDuration) -> Option<Self> {
if duration.is_positive() {
self.checked_sub(duration.unsigned_abs())
} else if duration.is_negative() {
self.checked_add(duration.unsigned_abs())
} else {
Some(*self)
}
}
#[inline]
fn signed_duration_since(&self, earlier: Self) -> SignedDuration {
match self.duration_since(earlier) {
Ok(duration) => duration.try_into().unwrap_or(SignedDuration::MAX),
Err(err) => {
let seconds = match i64::try_from(err.duration().as_secs()) {
Ok(seconds) => -seconds,
Err(_) => return SignedDuration::MIN,
};
let nanoseconds = -err.duration().subsec_nanos().cast_signed();
// Safety: `nanoseconds` is guaranteed to be between -999_999_999 and 0
// inclusive.
unsafe { SignedDuration::new_unchecked(seconds, nanoseconds) }
}
}
}
}
@@ -0,0 +1,144 @@
//! A format item with borrowed data.
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use core::fmt;
use crate::error;
use crate::format_description::Component;
/// A complete description of how to format and parse a type.
#[non_exhaustive]
#[cfg_attr(not(feature = "alloc"), derive(Debug))]
#[derive(Clone, Eq)]
pub enum BorrowedFormatItem<'a> {
/// Bytes that are formatted as-is.
///
/// **Note**: These bytes **should** be UTF-8, but are not required to be. The value is passed
/// through `String::from_utf8_lossy` when necessary.
#[deprecated(
since = "0.3.48",
note = "use `StringLiteral` instead; raw bytes are not recommended"
)]
Literal(&'a [u8]),
/// A string that is formatted as-is.
StringLiteral(&'a str),
/// A minimal representation of a single non-literal item.
Component(Component),
/// A series of literals or components that collectively form a partial or complete
/// description.
Compound(&'a [Self]),
/// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
/// will be no effect on the resulting `struct`.
///
/// This variant has no effect on formatting, as the value is guaranteed to be present.
Optional(&'a Self),
/// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
/// formatting, the first element of the slice is used. An empty slice is a no-op when
/// formatting or parsing.
First(&'a [Self]),
}
#[cfg(feature = "alloc")]
impl fmt::Debug for BorrowedFormatItem<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[expect(deprecated)]
Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
Self::StringLiteral(literal) => f.write_str(literal),
Self::Component(component) => component.fmt(f),
Self::Compound(compound) => compound.fmt(f),
Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
Self::First(items) => f.debug_tuple("First").field(items).finish(),
}
}
}
impl PartialEq for BorrowedFormatItem<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// trivial equality checks
#[expect(deprecated)]
(Self::Literal(a), Self::Literal(b)) => a == b,
(Self::StringLiteral(a), Self::StringLiteral(b)) => a == b,
(Self::Component(a), Self::Component(b)) => a == b,
(Self::Compound(a), Self::Compound(b)) => a == b,
(Self::Optional(a), Self::Optional(b)) => a == b,
(Self::First(a), Self::First(b)) => a == b,
// bytes vs string (back-compatibility)
#[expect(deprecated)]
(Self::Literal(a), Self::StringLiteral(b)) => *a == b.as_bytes(),
#[expect(deprecated)]
(Self::StringLiteral(a), Self::Literal(b)) => a.as_bytes() == *b,
_ => false,
}
}
}
impl From<Component> for BorrowedFormatItem<'_> {
#[inline]
fn from(component: Component) -> Self {
Self::Component(component)
}
}
impl TryFrom<BorrowedFormatItem<'_>> for Component {
type Error = error::DifferentVariant;
#[inline]
fn try_from(value: BorrowedFormatItem<'_>) -> Result<Self, Self::Error> {
match value {
BorrowedFormatItem::Component(component) => Ok(component),
_ => Err(error::DifferentVariant),
}
}
}
impl<'a> From<&'a [BorrowedFormatItem<'_>]> for BorrowedFormatItem<'a> {
#[inline]
fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self {
Self::Compound(items)
}
}
impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] {
type Error = error::DifferentVariant;
#[inline]
fn try_from(value: BorrowedFormatItem<'a>) -> Result<Self, Self::Error> {
match value {
BorrowedFormatItem::Compound(items) => Ok(items),
_ => Err(error::DifferentVariant),
}
}
}
impl PartialEq<Component> for BorrowedFormatItem<'_> {
#[inline]
fn eq(&self, rhs: &Component) -> bool {
matches!(self, Self::Component(component) if component == rhs)
}
}
impl PartialEq<BorrowedFormatItem<'_>> for Component {
#[inline]
fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
rhs == self
}
}
impl PartialEq<&[Self]> for BorrowedFormatItem<'_> {
#[inline]
fn eq(&self, rhs: &&[Self]) -> bool {
matches!(self, Self::Compound(compound) if compound == rhs)
}
}
impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] {
#[inline]
fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
rhs == self
}
}
@@ -0,0 +1,338 @@
//! Part of a format description.
use crate::format_description::modifier;
/// A component of a larger format description.
#[non_exhaustive]
#[allow(deprecated)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Component {
/// Day of the month.
Day(modifier::Day),
/// Month of the year in the abbreviated form (e.g. "Jan").
MonthShort(modifier::MonthShort),
/// Month of the year in the full form (e.g. "January").
MonthLong(modifier::MonthLong),
/// Month of the year in the numerical form (e.g. "1" for January).
MonthNumerical(modifier::MonthNumerical),
/// Ordinal day of the year.
Ordinal(modifier::Ordinal),
/// Weekday in the abbreviated form (e.g. "Mon").
WeekdayShort(modifier::WeekdayShort),
/// Weekday in the full form (e.g. "Monday").
WeekdayLong(modifier::WeekdayLong),
/// Weekday number where Sunday is either 0 or 1 depending on the modifier.
WeekdaySunday(modifier::WeekdaySunday),
/// Weekday number where Monday is either 0 or 1 depending on the modifier.
WeekdayMonday(modifier::WeekdayMonday),
/// Week number of the year, where week 1 starts is the week beginning on Monday that contains
/// January 4.
WeekNumberIso(modifier::WeekNumberIso),
/// Week number of the year, where week 1 starts on the first Sunday of the calendar year.
WeekNumberSunday(modifier::WeekNumberSunday),
/// Week number of the year, where week 1 starts on the first Monday of the calendar year.
WeekNumberMonday(modifier::WeekNumberMonday),
/// The calendar year. Supports the extended range.
CalendarYearFullExtendedRange(modifier::CalendarYearFullExtendedRange),
/// The calendar year. Does not support the extended range.
CalendarYearFullStandardRange(modifier::CalendarYearFullStandardRange),
/// The ISO week-based year. Supports the extended range.
IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange),
/// The ISO week-based year. Does not support the extended range.
IsoYearFullStandardRange(modifier::IsoYearFullStandardRange),
/// The century of the calendar year. Supports the extended range.
CalendarYearCenturyExtendedRange(modifier::CalendarYearCenturyExtendedRange),
/// The century of the calendar year. Does not support the extended range.
CalendarYearCenturyStandardRange(modifier::CalendarYearCenturyStandardRange),
/// The century of the ISO week-based year. Supports the extended range.
IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange),
/// The century of the ISO week-based year. Does not support the extended range.
IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange),
/// The last two digits of the calendar year.
CalendarYearLastTwo(modifier::CalendarYearLastTwo),
/// The last two digits of the ISO week-based year.
IsoYearLastTwo(modifier::IsoYearLastTwo),
/// Hour of the day using the 12-hour clock.
Hour12(modifier::Hour12),
/// Hour of the day using the 24-hour clock.
Hour24(modifier::Hour24),
/// Minute within the hour.
Minute(modifier::Minute),
/// AM/PM part of the time.
Period(modifier::Period),
/// Second within the minute.
Second(modifier::Second),
/// Subsecond within the second.
Subsecond(modifier::Subsecond),
/// Hour of the UTC offset.
OffsetHour(modifier::OffsetHour),
/// Minute within the hour of the UTC offset.
OffsetMinute(modifier::OffsetMinute),
/// Second within the minute of the UTC offset.
OffsetSecond(modifier::OffsetSecond),
/// A number of bytes to ignore when parsing. This has no effect on formatting.
Ignore(modifier::Ignore),
/// A Unix timestamp in seconds.
UnixTimestampSecond(modifier::UnixTimestampSecond),
/// A Unix timestamp in milliseconds.
UnixTimestampMillisecond(modifier::UnixTimestampMillisecond),
/// A Unix timestamp in microseconds.
UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond),
/// A Unix timestamp in nanoseconds.
UnixTimestampNanosecond(modifier::UnixTimestampNanosecond),
/// 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),
// Start of deprecated components that are no longer emitted by macros or parsers. They must
// be maintained for backward compatibility, as downstream users could have constructed them
// manually.
/// Month of the year.
#[deprecated(
since = "0.3.48",
note = "use `MonthShort`, `MonthLong`, or `MonthNumeric` instead"
)]
Month(modifier::Month),
/// Day of the week.
#[deprecated(
since = "0.3.48",
note = "use `WeekdayShort`, `WeekdayLong`, or `WeekdaySunday`, or `WeekdayMonday` instead"
)]
Weekday(modifier::Weekday),
/// Week within the year.
#[deprecated(
since = "0.3.48",
note = "use `WeekNumberIso`, `WeekNumberSunday`, or `WeekNumberMonday` instead"
)]
WeekNumber(modifier::WeekNumber),
/// Hour of the day.
#[deprecated(since = "0.3.48", note = "use `Hour12` or `Hour24` instead")]
Hour(modifier::Hour),
/// A Unix timestamp.
#[deprecated(
since = "0.3.48",
note = "use `UnixTimestampSeconds`, `UnixTimestampMilliseconds`, \
`UnixTimestampMicroseconds`, or `UnixTimestampNanoseconds` instead"
)]
UnixTimestamp(modifier::UnixTimestamp),
/// Year of the date.
#[deprecated(
since = "0.3.48",
note = "use one of the various `Year*` components instead"
)]
Year(modifier::Year),
}
impl From<Component> for super::format_description_v3::FormatDescriptionV3Inner<'_> {
#[inline]
fn from(component: Component) -> Self {
match component {
Component::Day(modifier) => Self::Day(modifier),
Component::MonthShort(modifier) => Self::MonthShort(modifier),
Component::MonthLong(modifier) => Self::MonthLong(modifier),
Component::MonthNumerical(modifier) => Self::MonthNumerical(modifier),
Component::Ordinal(modifier) => Self::Ordinal(modifier),
Component::WeekdayShort(modifier) => Self::WeekdayShort(modifier),
Component::WeekdayLong(modifier) => Self::WeekdayLong(modifier),
Component::WeekdaySunday(modifier) => Self::WeekdaySunday(modifier),
Component::WeekdayMonday(modifier) => Self::WeekdayMonday(modifier),
Component::WeekNumberIso(modifier) => Self::WeekNumberIso(modifier),
Component::WeekNumberSunday(modifier) => Self::WeekNumberSunday(modifier),
Component::WeekNumberMonday(modifier) => Self::WeekNumberMonday(modifier),
Component::CalendarYearFullExtendedRange(modifier) => {
Self::CalendarYearFullExtendedRange(modifier)
}
Component::CalendarYearFullStandardRange(modifier) => {
Self::CalendarYearFullStandardRange(modifier)
}
Component::IsoYearFullExtendedRange(modifier) => {
Self::IsoYearFullExtendedRange(modifier)
}
Component::IsoYearFullStandardRange(modifier) => {
Self::IsoYearFullStandardRange(modifier)
}
Component::CalendarYearCenturyExtendedRange(modifier) => {
Self::CalendarYearCenturyExtendedRange(modifier)
}
Component::CalendarYearCenturyStandardRange(modifier) => {
Self::CalendarYearCenturyStandardRange(modifier)
}
Component::IsoYearCenturyExtendedRange(modifier) => {
Self::IsoYearCenturyExtendedRange(modifier)
}
Component::IsoYearCenturyStandardRange(modifier) => {
Self::IsoYearCenturyStandardRange(modifier)
}
Component::CalendarYearLastTwo(modifier) => Self::CalendarYearLastTwo(modifier),
Component::IsoYearLastTwo(modifier) => Self::IsoYearLastTwo(modifier),
Component::Hour12(modifier) => Self::Hour12(modifier),
Component::Hour24(modifier) => Self::Hour24(modifier),
Component::Minute(modifier) => Self::Minute(modifier),
Component::Period(modifier) => Self::Period(modifier),
Component::Second(modifier) => Self::Second(modifier),
Component::Subsecond(modifier) => Self::Subsecond(modifier),
Component::OffsetHour(modifier) => Self::OffsetHour(modifier),
Component::OffsetMinute(modifier) => Self::OffsetMinute(modifier),
Component::OffsetSecond(modifier) => Self::OffsetSecond(modifier),
Component::Ignore(modifier) => Self::Ignore(modifier),
Component::UnixTimestampSecond(modifier) => Self::UnixTimestampSecond(modifier),
Component::UnixTimestampMillisecond(modifier) => {
Self::UnixTimestampMillisecond(modifier)
}
Component::UnixTimestampMicrosecond(modifier) => {
Self::UnixTimestampMicrosecond(modifier)
}
Component::UnixTimestampNanosecond(modifier) => Self::UnixTimestampNanosecond(modifier),
Component::End(modifier) => Self::End(modifier),
// Start of deprecated components.
#[expect(deprecated)]
Component::Month(modifier) => match modifier.repr {
modifier::MonthRepr::Short => Self::MonthShort(
modifier::MonthShort::default().with_case_sensitive(modifier.case_sensitive),
),
modifier::MonthRepr::Long => Self::MonthLong(
modifier::MonthLong::default().with_case_sensitive(modifier.case_sensitive),
),
modifier::MonthRepr::Numerical => Self::MonthNumerical(
modifier::MonthNumerical::default().with_padding(modifier.padding),
),
},
#[expect(deprecated)]
Component::Weekday(modifier) => match modifier.repr {
modifier::WeekdayRepr::Short => Self::WeekdayShort(
modifier::WeekdayShort::default().with_case_sensitive(modifier.case_sensitive),
),
modifier::WeekdayRepr::Long => Self::WeekdayLong(
modifier::WeekdayLong::default().with_case_sensitive(modifier.case_sensitive),
),
modifier::WeekdayRepr::Sunday => Self::WeekdaySunday(
modifier::WeekdaySunday::default().with_one_indexed(modifier.one_indexed),
),
modifier::WeekdayRepr::Monday => Self::WeekdayMonday(
modifier::WeekdayMonday::default().with_one_indexed(modifier.one_indexed),
),
},
#[expect(deprecated)]
Component::WeekNumber(modifier) => match modifier.repr {
modifier::WeekNumberRepr::Iso => Self::WeekNumberIso(
modifier::WeekNumberIso::default().with_padding(modifier.padding),
),
modifier::WeekNumberRepr::Sunday => Self::WeekNumberSunday(
modifier::WeekNumberSunday::default().with_padding(modifier.padding),
),
modifier::WeekNumberRepr::Monday => Self::WeekNumberMonday(
modifier::WeekNumberMonday::default().with_padding(modifier.padding),
),
},
#[expect(deprecated)]
Component::Hour(modifier) => {
if modifier.is_12_hour_clock {
Self::Hour12(modifier::Hour12::default().with_padding(modifier.padding))
} else {
Self::Hour24(modifier::Hour24::default().with_padding(modifier.padding))
}
}
#[expect(deprecated)]
Component::UnixTimestamp(modifier) => match modifier.precision {
modifier::UnixTimestampPrecision::Second => Self::UnixTimestampSecond(
modifier::UnixTimestampSecond::default()
.with_sign_is_mandatory(modifier.sign_is_mandatory),
),
modifier::UnixTimestampPrecision::Millisecond => Self::UnixTimestampMillisecond(
modifier::UnixTimestampMillisecond::default()
.with_sign_is_mandatory(modifier.sign_is_mandatory),
),
modifier::UnixTimestampPrecision::Microsecond => Self::UnixTimestampMicrosecond(
modifier::UnixTimestampMicrosecond::default()
.with_sign_is_mandatory(modifier.sign_is_mandatory),
),
modifier::UnixTimestampPrecision::Nanosecond => Self::UnixTimestampNanosecond(
modifier::UnixTimestampNanosecond::default()
.with_sign_is_mandatory(modifier.sign_is_mandatory),
),
},
#[expect(deprecated)]
Component::Year(modifier) => {
match (modifier.iso_week_based, modifier.repr, modifier.range) {
(true, modifier::YearRepr::Full, modifier::YearRange::Standard) => {
Self::IsoYearFullStandardRange(
modifier::IsoYearFullStandardRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(true, modifier::YearRepr::Full, modifier::YearRange::Extended) => {
Self::IsoYearFullExtendedRange(
modifier::IsoYearFullExtendedRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(false, modifier::YearRepr::Full, modifier::YearRange::Standard) => {
Self::CalendarYearFullStandardRange(
modifier::CalendarYearFullStandardRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(false, modifier::YearRepr::Full, modifier::YearRange::Extended) => {
Self::CalendarYearFullExtendedRange(
modifier::CalendarYearFullExtendedRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(true, modifier::YearRepr::Century, modifier::YearRange::Standard) => {
Self::IsoYearCenturyStandardRange(
modifier::IsoYearCenturyStandardRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(true, modifier::YearRepr::Century, modifier::YearRange::Extended) => {
Self::IsoYearCenturyExtendedRange(
modifier::IsoYearCenturyExtendedRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(false, modifier::YearRepr::Century, modifier::YearRange::Standard) => {
Self::CalendarYearCenturyStandardRange(
modifier::CalendarYearCenturyStandardRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(false, modifier::YearRepr::Century, modifier::YearRange::Extended) => {
Self::CalendarYearCenturyExtendedRange(
modifier::CalendarYearCenturyExtendedRange::default()
.with_padding(modifier.padding)
.with_sign_is_mandatory(modifier.sign_is_mandatory),
)
}
(true, modifier::YearRepr::LastTwo, modifier::YearRange::Standard) => {
Self::IsoYearLastTwo(
modifier::IsoYearLastTwo::default().with_padding(modifier.padding),
)
}
(true, modifier::YearRepr::LastTwo, modifier::YearRange::Extended) => {
Self::IsoYearLastTwo(
modifier::IsoYearLastTwo::default().with_padding(modifier.padding),
)
}
(false, modifier::YearRepr::LastTwo, modifier::YearRange::Standard) => {
Self::CalendarYearLastTwo(
modifier::CalendarYearLastTwo::default().with_padding(modifier.padding),
)
}
(false, modifier::YearRepr::LastTwo, modifier::YearRange::Extended) => {
Self::CalendarYearLastTwo(
modifier::CalendarYearLastTwo::default().with_padding(modifier.padding),
)
}
}
}
}
}
}
@@ -0,0 +1,544 @@
//! A version 3 format description.
//!
//! Unlike versions 1 and 2, this is opaque so as to permit any changes necessary without breaking
//! downstream users. Other than `FormatDescriptionV3`, all items are internal.
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::fmt;
use crate::format_description::modifier;
/// A complete description of how to format and parse a type.
///
/// Both for forwards compatibility and to enable optimizations, this type is deliberately opaque
/// and cannot be constructed by users of the crate. Instead, it is returned by the
/// `format_description!` macro (when `version=3` is used) as well as the `parse_borrowed` and
/// `parse_owned` methods.
#[derive(Clone)]
pub struct FormatDescriptionV3<'a> {
/// The inner `enum` that controls all business logic.
pub(crate) inner: FormatDescriptionV3Inner<'a>,
/// The maximum number of bytes that are needed to format any value using this format
/// description.
#[cfg(feature = "formatting")]
pub(crate) max_bytes_needed: usize,
}
impl fmt::Debug for FormatDescriptionV3<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl FormatDescriptionV3<'_> {
/// Convert the format description to an owned version, enabling it to be stored without regard
/// for lifetime.
#[cfg(feature = "alloc")]
#[inline]
pub fn to_owned(self) -> FormatDescriptionV3<'static> {
FormatDescriptionV3 {
inner: self.inner.into_owned(),
#[cfg(feature = "formatting")]
max_bytes_needed: self.max_bytes_needed,
}
}
}
/// The inner `enum` of a version 3 format description. Controls all business logic.
// public via `crate::format_description::__private` for macro use
#[non_exhaustive]
#[derive(Clone)]
pub enum FormatDescriptionV3Inner<'a> {
/// Day of the month.
Day(modifier::Day),
/// Month of the year in the abbreviated form (e.g. "Jan").
MonthShort(modifier::MonthShort),
/// Month of the year in the full form (e.g. "January").
MonthLong(modifier::MonthLong),
/// Month of the year in the numerical form (e.g. "1" for January).
MonthNumerical(modifier::MonthNumerical),
/// Ordinal day of the year.
Ordinal(modifier::Ordinal),
/// Weekday in the abbreviated form (e.g. "Mon").
WeekdayShort(modifier::WeekdayShort),
/// Weekday in the full form (e.g. "Monday").
WeekdayLong(modifier::WeekdayLong),
/// Weekday number where Sunday is either 0 or 1 depending on the modifier.
WeekdaySunday(modifier::WeekdaySunday),
/// Weekday number where Monday is either 0 or 1 depending on the modifier.
WeekdayMonday(modifier::WeekdayMonday),
/// Week number of the year, where week 1 starts is the week beginning on Monday that contains
/// January 4.
WeekNumberIso(modifier::WeekNumberIso),
/// Week number of the year, where week 1 starts on the first Sunday of the calendar year.
WeekNumberSunday(modifier::WeekNumberSunday),
/// Week number of the year, where week 1 starts on the first Monday of the calendar year.
WeekNumberMonday(modifier::WeekNumberMonday),
/// The calendar year. Supports the extended range.
CalendarYearFullExtendedRange(modifier::CalendarYearFullExtendedRange),
/// The calendar year. Does not support the extended range.
CalendarYearFullStandardRange(modifier::CalendarYearFullStandardRange),
/// The ISO week-based year. Supports the extended range.
IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange),
/// The ISO week-based year. Does not support the extended range.
IsoYearFullStandardRange(modifier::IsoYearFullStandardRange),
/// The century of the calendar year. Supports the extended range.
CalendarYearCenturyExtendedRange(modifier::CalendarYearCenturyExtendedRange),
/// The century of the calendar year. Does not support the extended range.
CalendarYearCenturyStandardRange(modifier::CalendarYearCenturyStandardRange),
/// The century of the ISO week-based year. Supports the extended range.
IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange),
/// The century of the ISO week-based year. Does not support the extended range.
IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange),
/// The last two digits of the calendar year.
CalendarYearLastTwo(modifier::CalendarYearLastTwo),
/// The last two digits of the ISO week-based year.
IsoYearLastTwo(modifier::IsoYearLastTwo),
/// Hour of the day using the 12-hour clock.
Hour12(modifier::Hour12),
/// Hour of the day using the 24-hour clock.
Hour24(modifier::Hour24),
/// Minute within the hour.
Minute(modifier::Minute),
/// AM/PM part of the time.
Period(modifier::Period),
/// Second within the minute.
Second(modifier::Second),
/// Subsecond within the second.
Subsecond(modifier::Subsecond),
/// Hour of the UTC offset.
OffsetHour(modifier::OffsetHour),
/// Minute within the hour of the UTC offset.
OffsetMinute(modifier::OffsetMinute),
/// Second within the minute of the UTC offset.
OffsetSecond(modifier::OffsetSecond),
/// A number of bytes to ignore when parsing. This has no effect on formatting.
Ignore(modifier::Ignore),
/// A Unix timestamp in seconds.
UnixTimestampSecond(modifier::UnixTimestampSecond),
/// A Unix timestamp in milliseconds.
UnixTimestampMillisecond(modifier::UnixTimestampMillisecond),
/// A Unix timestamp in microseconds.
UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond),
/// A Unix timestamp in nanoseconds.
UnixTimestampNanosecond(modifier::UnixTimestampNanosecond),
/// 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),
/// A string that is formatted as-is.
BorrowedLiteral(&'a str),
/// A series of literals or components that collectively form a partial or complete description.
BorrowedCompound(&'a [Self]),
/// An item that may or may not be present when parsing. If parsing fails, there will be no
/// effect on the resulting `struct`.
BorrowedOptional {
/// Whether the item should be formatted.
format: bool,
/// The item in question.
item: &'a Self,
},
/// A series of items where, when parsing, the first successful parse is used. When formatting,
/// the first item is used. If no items are present, both formatting and parsing are no-ops.
BorrowedFirst(&'a [Self]),
/// A string that is formatted as-is.
#[cfg(feature = "alloc")]
OwnedLiteral(Box<str>),
/// A series of literals or components that collectively form a partial or complete description.
#[cfg(feature = "alloc")]
OwnedCompound(Box<[Self]>),
/// An item that may or may not be present when parsing. If parsing fails, there will be no
/// effect on the resulting `struct`.
#[cfg(feature = "alloc")]
OwnedOptional {
/// Whether the item should be formatted.
format: bool,
/// The item in question.
item: Box<Self>,
},
/// A series of items where, when parsing, the first successful parse is used. When formatting,
/// the first item is used. If no items are present, both formatting and parsing are no-ops.
#[cfg(feature = "alloc")]
OwnedFirst(Box<[Self]>),
}
impl fmt::Debug for FormatDescriptionV3Inner<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Day(modifier) => modifier.fmt(f),
Self::MonthShort(modifier) => modifier.fmt(f),
Self::MonthLong(modifier) => modifier.fmt(f),
Self::MonthNumerical(modifier) => modifier.fmt(f),
Self::Ordinal(modifier) => modifier.fmt(f),
Self::WeekdayShort(modifier) => modifier.fmt(f),
Self::WeekdayLong(modifier) => modifier.fmt(f),
Self::WeekdaySunday(modifier) => modifier.fmt(f),
Self::WeekdayMonday(modifier) => modifier.fmt(f),
Self::WeekNumberIso(modifier) => modifier.fmt(f),
Self::WeekNumberSunday(modifier) => modifier.fmt(f),
Self::WeekNumberMonday(modifier) => modifier.fmt(f),
Self::CalendarYearFullExtendedRange(modifier) => modifier.fmt(f),
Self::CalendarYearFullStandardRange(modifier) => modifier.fmt(f),
Self::IsoYearFullExtendedRange(modifier) => modifier.fmt(f),
Self::IsoYearFullStandardRange(modifier) => modifier.fmt(f),
Self::CalendarYearCenturyExtendedRange(modifier) => modifier.fmt(f),
Self::CalendarYearCenturyStandardRange(modifier) => modifier.fmt(f),
Self::IsoYearCenturyExtendedRange(modifier) => modifier.fmt(f),
Self::IsoYearCenturyStandardRange(modifier) => modifier.fmt(f),
Self::CalendarYearLastTwo(modifier) => modifier.fmt(f),
Self::IsoYearLastTwo(modifier) => modifier.fmt(f),
Self::Hour12(modifier) => modifier.fmt(f),
Self::Hour24(modifier) => modifier.fmt(f),
Self::Minute(modifier) => modifier.fmt(f),
Self::Period(modifier) => modifier.fmt(f),
Self::Second(modifier) => modifier.fmt(f),
Self::Subsecond(modifier) => modifier.fmt(f),
Self::OffsetHour(modifier) => modifier.fmt(f),
Self::OffsetMinute(modifier) => modifier.fmt(f),
Self::OffsetSecond(modifier) => modifier.fmt(f),
Self::Ignore(modifier) => modifier.fmt(f),
Self::UnixTimestampSecond(modifier) => modifier.fmt(f),
Self::UnixTimestampMillisecond(modifier) => modifier.fmt(f),
Self::UnixTimestampMicrosecond(modifier) => modifier.fmt(f),
Self::UnixTimestampNanosecond(modifier) => modifier.fmt(f),
Self::End(modifier) => modifier.fmt(f),
Self::BorrowedLiteral(literal) => f.debug_tuple("Literal").field(literal).finish(),
Self::BorrowedCompound(compound) => f.debug_tuple("Compound").field(compound).finish(),
Self::BorrowedOptional {
format: should_format,
item,
} => f
.debug_struct("Optional")
.field("should_format", should_format)
.field("item", item)
.finish(),
Self::BorrowedFirst(items) => f.debug_tuple("First").field(items).finish(),
#[cfg(feature = "alloc")]
Self::OwnedLiteral(literal) => f.debug_tuple("Literal").field(literal).finish(),
#[cfg(feature = "alloc")]
Self::OwnedCompound(compound) => f.debug_tuple("Compound").field(compound).finish(),
#[cfg(feature = "alloc")]
Self::OwnedOptional {
format: should_format,
item,
} => f
.debug_struct("Optional")
.field("should_format", should_format)
.field("item", item)
.finish(),
#[cfg(feature = "alloc")]
Self::OwnedFirst(items) => f.debug_tuple("First").field(items).finish(),
}
}
}
impl<'a> FormatDescriptionV3Inner<'a> {
/// Recursively convert to an owned version, doing so in-place when possible.
#[cfg(feature = "alloc")]
fn make_owned_in_place(&mut self) {
use alloc::borrow::ToOwned as _;
use alloc::boxed::Box;
match self {
Self::BorrowedLiteral(literal) => {
*self = Self::OwnedLiteral(literal.to_owned().into_boxed_str());
}
Self::BorrowedCompound(compound) => {
*self = Self::OwnedCompound(
compound
.iter()
.cloned()
.map(|item| item.into_owned())
.collect(),
);
}
Self::BorrowedOptional { format, item } => {
*self = Self::OwnedOptional {
format: *format,
item: Box::new(item.clone().into_owned()),
};
}
Self::BorrowedFirst(items) => {
*self = Self::OwnedFirst(
items
.iter()
.cloned()
.map(|item| item.into_owned())
.collect(),
);
}
Self::OwnedCompound(compound) => {
for item in compound {
item.make_owned_in_place();
}
}
Self::OwnedOptional { format: _, item } => {
item.make_owned_in_place();
}
Self::OwnedFirst(items) => {
for item in items {
item.make_owned_in_place();
}
}
FormatDescriptionV3Inner::Day(_)
| FormatDescriptionV3Inner::MonthShort(_)
| FormatDescriptionV3Inner::MonthLong(_)
| FormatDescriptionV3Inner::MonthNumerical(_)
| FormatDescriptionV3Inner::Ordinal(_)
| FormatDescriptionV3Inner::WeekdayShort(_)
| FormatDescriptionV3Inner::WeekdayLong(_)
| FormatDescriptionV3Inner::WeekdaySunday(_)
| FormatDescriptionV3Inner::WeekdayMonday(_)
| FormatDescriptionV3Inner::WeekNumberIso(_)
| FormatDescriptionV3Inner::WeekNumberSunday(_)
| FormatDescriptionV3Inner::WeekNumberMonday(_)
| FormatDescriptionV3Inner::CalendarYearFullExtendedRange(_)
| FormatDescriptionV3Inner::CalendarYearFullStandardRange(_)
| FormatDescriptionV3Inner::IsoYearFullExtendedRange(_)
| FormatDescriptionV3Inner::IsoYearFullStandardRange(_)
| FormatDescriptionV3Inner::CalendarYearCenturyExtendedRange(_)
| FormatDescriptionV3Inner::CalendarYearCenturyStandardRange(_)
| FormatDescriptionV3Inner::IsoYearCenturyExtendedRange(_)
| FormatDescriptionV3Inner::IsoYearCenturyStandardRange(_)
| FormatDescriptionV3Inner::CalendarYearLastTwo(_)
| FormatDescriptionV3Inner::IsoYearLastTwo(_)
| FormatDescriptionV3Inner::Hour12(_)
| FormatDescriptionV3Inner::Hour24(_)
| FormatDescriptionV3Inner::Minute(_)
| FormatDescriptionV3Inner::Period(_)
| FormatDescriptionV3Inner::Second(_)
| FormatDescriptionV3Inner::Subsecond(_)
| FormatDescriptionV3Inner::OffsetHour(_)
| FormatDescriptionV3Inner::OffsetMinute(_)
| FormatDescriptionV3Inner::OffsetSecond(_)
| FormatDescriptionV3Inner::Ignore(_)
| FormatDescriptionV3Inner::UnixTimestampSecond(_)
| FormatDescriptionV3Inner::UnixTimestampMillisecond(_)
| FormatDescriptionV3Inner::UnixTimestampMicrosecond(_)
| FormatDescriptionV3Inner::UnixTimestampNanosecond(_)
| FormatDescriptionV3Inner::End(_)
| FormatDescriptionV3Inner::OwnedLiteral(_) => {
// no-op, as these variants do not contain any references
}
}
}
/// Convert the format description to an owned version in place, replacing borrowed
/// components with their owned equivalents.
#[cfg(feature = "alloc")]
pub(super) fn into_owned(mut self) -> FormatDescriptionV3Inner<'static> {
self.make_owned_in_place();
// Safety: `make_owned_in_place` recursively eliminates all variants that contain
// references, so we can transmute between lifetimes freely. ADTs do not vary in layout when
// only the lifetime differs.
unsafe {
core::mem::transmute::<FormatDescriptionV3Inner<'a>, FormatDescriptionV3Inner<'static>>(
self,
)
}
}
/// Convert the inner `enum` to a `FormatDescriptionV3`.
#[inline]
pub const fn into_opaque(self) -> FormatDescriptionV3<'a> {
FormatDescriptionV3 {
#[cfg(feature = "formatting")]
max_bytes_needed: self.max_bytes_needed(),
inner: self,
}
}
/// Obtain the maximum number of bytes that are needed to format any value using this format
/// description.
#[cfg(feature = "formatting")]
const fn max_bytes_needed(&self) -> usize {
match self {
Self::Day(_) => 2,
Self::MonthShort(_) => 3,
Self::MonthLong(_) => 9,
Self::MonthNumerical(_) => 2,
Self::Ordinal(_) => 3,
Self::WeekdayShort(_) => 3,
Self::WeekdayLong(_) => 9,
Self::WeekdaySunday(_) | Self::WeekdayMonday(_) => 1,
Self::WeekNumberIso(_) | Self::WeekNumberSunday(_) | Self::WeekNumberMonday(_) => 2,
Self::CalendarYearFullExtendedRange(_) => 7,
Self::CalendarYearFullStandardRange(_) => 5,
Self::IsoYearFullExtendedRange(_) => 7,
Self::IsoYearFullStandardRange(_) => 5,
Self::CalendarYearCenturyExtendedRange(_) => 5,
Self::CalendarYearCenturyStandardRange(_) => 3,
Self::IsoYearCenturyExtendedRange(_) => 5,
Self::IsoYearCenturyStandardRange(_) => 3,
Self::CalendarYearLastTwo(_) => 2,
Self::IsoYearLastTwo(_) => 2,
Self::Hour12(_) | Self::Hour24(_) => 2,
Self::Minute(_) | Self::Period(_) | Self::Second(_) => 2,
Self::Subsecond(modifier) => match modifier.digits {
modifier::SubsecondDigits::One => 1,
modifier::SubsecondDigits::Two => 2,
modifier::SubsecondDigits::Three => 3,
modifier::SubsecondDigits::Four => 4,
modifier::SubsecondDigits::Five => 5,
modifier::SubsecondDigits::Six => 6,
modifier::SubsecondDigits::Seven => 7,
modifier::SubsecondDigits::Eight => 8,
modifier::SubsecondDigits::Nine => 9,
modifier::SubsecondDigits::OneOrMore => 9,
},
Self::OffsetHour(_) => 3,
Self::OffsetMinute(_) | Self::OffsetSecond(_) => 2,
#[cfg(feature = "large-dates")]
Self::UnixTimestampSecond(_) => 15,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampSecond(_) => 13,
#[cfg(feature = "large-dates")]
Self::UnixTimestampMillisecond(_) => 18,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampMillisecond(_) => 16,
#[cfg(feature = "large-dates")]
Self::UnixTimestampMicrosecond(_) => 21,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampMicrosecond(_) => 19,
#[cfg(feature = "large-dates")]
Self::UnixTimestampNanosecond(_) => 24,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampNanosecond(_) => 22,
Self::Ignore(_) | Self::End(_) => 0,
FormatDescriptionV3Inner::BorrowedLiteral(s) => s.len(),
FormatDescriptionV3Inner::BorrowedCompound(items) => {
let mut max_bytes_needed = 0;
let mut idx = 0;
while idx < items.len() {
max_bytes_needed += items[idx].max_bytes_needed();
idx += 1;
}
max_bytes_needed
}
FormatDescriptionV3Inner::BorrowedOptional { format, item } => {
if *format {
item.max_bytes_needed()
} else {
0
}
}
FormatDescriptionV3Inner::BorrowedFirst(items) => {
if items.is_empty() {
0
} else {
items[0].max_bytes_needed()
}
}
FormatDescriptionV3Inner::OwnedLiteral(s) => s.len(),
FormatDescriptionV3Inner::OwnedCompound(items) => {
let mut max_bytes_needed = 0;
let mut idx = 0;
while idx < items.len() {
max_bytes_needed += items[idx].max_bytes_needed();
idx += 1;
}
max_bytes_needed
}
FormatDescriptionV3Inner::OwnedOptional { format, item } => {
if *format {
item.max_bytes_needed()
} else {
0
}
}
FormatDescriptionV3Inner::OwnedFirst(items) => {
if items.is_empty() {
0
} else {
items[0].max_bytes_needed()
}
}
}
}
}
/// A component of a larger format description.
// public via `crate::format_description::__private` for macro use
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum Component {
/// Day of the month.
Day(modifier::Day),
/// Month of the year in the abbreviated form (e.g. "Jan").
MonthShort(modifier::MonthShort),
/// Month of the year in the full form (e.g. "January").
MonthLong(modifier::MonthLong),
/// Month of the year in the numerical form (e.g. "1" for January).
MonthNumerical(modifier::MonthNumerical),
/// Ordinal day of the year.
Ordinal(modifier::Ordinal),
/// Weekday in the abbreviated form (e.g. "Mon").
WeekdayShort(modifier::WeekdayShort),
/// Weekday in the full form (e.g. "Monday").
WeekdayLong(modifier::WeekdayLong),
/// Weekday number where Sunday is either 0 or 1 depending on the modifier.
WeekdaySunday(modifier::WeekdaySunday),
/// Weekday number where Monday is either 0 or 1 depending on the modifier.
WeekdayMonday(modifier::WeekdayMonday),
/// Week number of the year, where week 1 starts is the week beginning on Monday that contains
/// January 4.
WeekNumberIso(modifier::WeekNumberIso),
/// Week number of the year, where week 1 starts on the first Sunday of the calendar year.
WeekNumberSunday(modifier::WeekNumberSunday),
/// Week number of the year, where week 1 starts on the first Monday of the calendar year.
WeekNumberMonday(modifier::WeekNumberMonday),
/// The calendar year. Supports the extended range.
CalendarYearFullExtendedRange(modifier::CalendarYearFullExtendedRange),
/// The calendar year. Does not support the extended range.
CalendarYearFullStandardRange(modifier::CalendarYearFullStandardRange),
/// The ISO week-based year. Supports the extended range.
IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange),
/// The ISO week-based year. Does not support the extended range.
IsoYearFullStandardRange(modifier::IsoYearFullStandardRange),
/// The century of the calendar year. Supports the extended range.
CalendarYearCenturyExtendedRange(modifier::CalendarYearCenturyExtendedRange),
/// The century of the calendar year. Does not support the extended range.
CalendarYearCenturyStandardRange(modifier::CalendarYearCenturyStandardRange),
/// The century of the ISO week-based year. Supports the extended range.
IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange),
/// The century of the ISO week-based year. Does not support the extended range.
IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange),
/// The last two digits of the calendar year.
CalendarYearLastTwo(modifier::CalendarYearLastTwo),
/// The last two digits of the ISO week-based year.
IsoYearLastTwo(modifier::IsoYearLastTwo),
/// Hour of the day using the 12-hour clock.
Hour12(modifier::Hour12),
/// Hour of the day using the 24-hour clock.
Hour24(modifier::Hour24),
/// Minute within the hour.
Minute(modifier::Minute),
/// AM/PM part of the time.
Period(modifier::Period),
/// Second within the minute.
Second(modifier::Second),
/// Subsecond within the second.
Subsecond(modifier::Subsecond),
/// Hour of the UTC offset.
OffsetHour(modifier::OffsetHour),
/// Minute within the hour of the UTC offset.
OffsetMinute(modifier::OffsetMinute),
/// Second within the minute of the UTC offset.
OffsetSecond(modifier::OffsetSecond),
/// A number of bytes to ignore when parsing. This has no effect on formatting.
Ignore(modifier::Ignore),
/// A Unix timestamp in seconds.
UnixTimestampSecond(modifier::UnixTimestampSecond),
/// A Unix timestamp in milliseconds.
UnixTimestampMillisecond(modifier::UnixTimestampMillisecond),
/// A Unix timestamp in microseconds.
UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond),
/// A Unix timestamp in nanoseconds.
UnixTimestampNanosecond(modifier::UnixTimestampNanosecond),
/// 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),
}
+66
View File
@@ -0,0 +1,66 @@
//! Description of how types should be formatted and parsed.
//!
//! The formatted value will be output to the provided writer. Format descriptions can be
//! [well-known](crate::format_description::well_known) or obtained by using the
//! [`format_description!`](crate::macros::format_description) macro or a function listed below.
//!
//! For examples, see the implementors of [Formattable](crate::formatting::Formattable),
//! e.g. [`well_known::Rfc3339`].
mod borrowed_format_item;
mod component;
pub(crate) mod format_description_v3;
pub mod modifier;
#[cfg(feature = "alloc")]
mod owned_format_item;
#[cfg(feature = "alloc")]
mod parse;
/// Well-known formats, typically standards.
pub mod well_known {
pub mod iso8601;
mod rfc2822;
mod rfc3339;
#[doc(inline)]
pub use iso8601::Iso8601;
pub use rfc2822::Rfc2822;
pub use rfc3339::Rfc3339;
}
/// Re-exports of internal types for use in macros.
///
/// Do not rely on the existence of this module for any reason. It, in its entirety, is not
/// considered public API, is not subject to semantic versioning, and may be changed or removed at
/// any point.
#[doc(hidden)]
pub mod __private {
pub use super::format_description_v3::{Component, FormatDescriptionV3Inner};
}
pub use borrowed_format_item::BorrowedFormatItem;
#[doc(hidden)]
#[deprecated(since = "0.3.37", note = "use `BorrowedFormatItem` for clarity")]
pub use borrowed_format_item::BorrowedFormatItem as FormatItem;
#[cfg(feature = "alloc")]
pub use owned_format_item::OwnedFormatItem;
pub use self::component::Component;
pub use self::format_description_v3::FormatDescriptionV3;
#[cfg(feature = "alloc")]
#[expect(deprecated, reason = "backwards compatibility")]
pub use self::parse::parse;
#[cfg(feature = "alloc")]
pub use self::parse::{parse_borrowed, parse_owned, parse_strftime_borrowed, parse_strftime_owned};
/// The type output by the [`format_description!`](crate::macros::format_description) macro.
pub type StaticFormatDescription = &'static [BorrowedFormatItem<'static>];
/// Indicate whether the hour is "am" or "pm".
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Period {
#[allow(clippy::missing_docs_in_private_items)]
Am,
#[allow(clippy::missing_docs_in_private_items)]
Pm,
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,190 @@
//! A format item with owned data.
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::error;
use crate::format_description::{BorrowedFormatItem, Component};
/// A complete description of how to format and parse a type.
#[non_exhaustive]
#[derive(Clone, Eq)]
pub enum OwnedFormatItem {
/// Bytes that are formatted as-is.
///
/// **Note**: These bytes **should** be UTF-8, but are not required to be. The value is passed
/// through `String::from_utf8_lossy` when necessary.
#[deprecated(
since = "0.3.48",
note = "use `StringLiteral` instead; raw bytes are not recommended"
)]
Literal(Box<[u8]>),
/// A string that is formatted as-is.
StringLiteral(Box<str>),
/// A minimal representation of a single non-literal item.
Component(Component),
/// A series of literals or components that collectively form a partial or complete
/// description.
Compound(Box<[Self]>),
/// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
/// will be no effect on the resulting `struct`.
///
/// This variant has no effect on formatting, as the value is guaranteed to be present.
Optional(Box<Self>),
/// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
/// formatting, the first element of the [`Vec`] is used. An empty [`Vec`] is a no-op when
/// formatting or parsing.
First(Box<[Self]>),
}
impl fmt::Debug for OwnedFormatItem {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[expect(deprecated)]
Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
Self::StringLiteral(literal) => f.write_str(literal),
Self::Component(component) => component.fmt(f),
Self::Compound(compound) => compound.fmt(f),
Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
Self::First(items) => f.debug_tuple("First").field(items).finish(),
}
}
}
impl PartialEq for OwnedFormatItem {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// trivial equality checks
#[expect(deprecated)]
(Self::Literal(a), Self::Literal(b)) => a == b,
(Self::StringLiteral(a), Self::StringLiteral(b)) => a == b,
(Self::Component(a), Self::Component(b)) => a == b,
(Self::Compound(a), Self::Compound(b)) => a == b,
(Self::Optional(a), Self::Optional(b)) => a == b,
(Self::First(a), Self::First(b)) => a == b,
// bytes vs string (back-compatibility)
#[expect(deprecated)]
(Self::Literal(a), Self::StringLiteral(b)) => &**a == b.as_bytes(),
#[expect(deprecated)]
(Self::StringLiteral(a), Self::Literal(b)) => a.as_bytes() == &**b,
_ => false,
}
}
}
impl From<BorrowedFormatItem<'_>> for OwnedFormatItem {
#[inline]
fn from(item: BorrowedFormatItem<'_>) -> Self {
(&item).into()
}
}
impl From<&BorrowedFormatItem<'_>> for OwnedFormatItem {
#[inline]
fn from(item: &BorrowedFormatItem<'_>) -> Self {
match item {
#[expect(deprecated)]
BorrowedFormatItem::Literal(literal) => {
Self::Literal(literal.to_vec().into_boxed_slice())
}
BorrowedFormatItem::StringLiteral(literal) => {
use alloc::borrow::ToOwned as _;
Self::StringLiteral((*literal).to_owned().into_boxed_str())
}
BorrowedFormatItem::Component(component) => Self::Component(*component),
BorrowedFormatItem::Compound(compound) => {
Self::Compound(compound.iter().cloned().map(Into::into).collect())
}
BorrowedFormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
BorrowedFormatItem::First(items) => {
Self::First(items.iter().cloned().map(Into::into).collect())
}
}
}
}
impl From<Vec<BorrowedFormatItem<'_>>> for OwnedFormatItem {
#[inline]
fn from(items: Vec<BorrowedFormatItem<'_>>) -> Self {
items.as_slice().into()
}
}
impl<'a, T> From<&T> for OwnedFormatItem
where
T: AsRef<[BorrowedFormatItem<'a>]> + ?Sized,
{
#[inline]
fn from(items: &T) -> Self {
Self::Compound(items.as_ref().iter().cloned().map(Into::into).collect())
}
}
impl From<Component> for OwnedFormatItem {
#[inline]
fn from(component: Component) -> Self {
Self::Component(component)
}
}
impl TryFrom<OwnedFormatItem> for Component {
type Error = error::DifferentVariant;
#[inline]
fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
match value {
OwnedFormatItem::Component(component) => Ok(component),
_ => Err(error::DifferentVariant),
}
}
}
impl From<Vec<Self>> for OwnedFormatItem {
#[inline]
fn from(items: Vec<Self>) -> Self {
Self::Compound(items.into_boxed_slice())
}
}
impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> {
type Error = error::DifferentVariant;
#[inline]
fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
match value {
OwnedFormatItem::Compound(items) => Ok(items.into_vec()),
_ => Err(error::DifferentVariant),
}
}
}
impl PartialEq<Component> for OwnedFormatItem {
#[inline]
fn eq(&self, rhs: &Component) -> bool {
matches!(self, Self::Component(component) if component == rhs)
}
}
impl PartialEq<OwnedFormatItem> for Component {
#[inline]
fn eq(&self, rhs: &OwnedFormatItem) -> bool {
rhs == self
}
}
impl PartialEq<&[Self]> for OwnedFormatItem {
#[inline]
fn eq(&self, rhs: &&[Self]) -> bool {
matches!(self, Self::Compound(compound) if &&**compound == rhs)
}
}
impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] {
#[inline]
fn eq(&self, rhs: &OwnedFormatItem) -> bool {
rhs == self
}
}
@@ -0,0 +1,868 @@
//! Typed, validated representation of a parsed format description.
use alloc::borrow::ToOwned as _;
use core::num::NonZero;
use core::str::{self, FromStr};
use super::lexer_ast::Modifier;
use super::{Error, OptionExt as _, Span, Spanned, SpannedValue as _, unused};
use crate::error::InvalidFormatDescription;
use crate::format_description::__private::FormatDescriptionV3Inner;
use crate::hint;
use crate::internal_macros::{bug, try_likely_ok};
macro_rules! parse_modifiers {
($version:expr, $modifiers:expr, struct { $($field:ident : $modifier:ident),* $(,)? }) => {
'block: {
struct Parsed {
$($field: Option<Spanned<<$modifier as ModifierValue>::Type>>),*
}
let mut parsed = Parsed {
$($field: None),*
};
for modifier in $modifiers {
$(if ident_eq::<VERSION>(&modifier.key, stringify!($field)) {
hint::cold_path();
if version!(3..) && parsed.$field.is_some() {
break 'block Err(Error {
_inner: unused(modifier.key_span().error("duplicate modifier key")),
public: InvalidFormatDescription::DuplicateModifier {
name: stringify!($field),
index: modifier.key.location.byte as usize,
}
});
}
match <$modifier>::from_modifier_value::<VERSION>(
|| modifier.value_span(),
modifier.value,
) {
Ok(value) => {
parsed.$field = Some(
<<$modifier as ModifierValue>::Type>::from(value)
.spanned(modifier.value_span())
)
},
Err(err) => {
hint::cold_path();
break 'block Err(err)
},
}
continue;
})*
hint::cold_path();
break 'block Err(Error {
_inner: unused(modifier.key_span().error("invalid modifier key")),
public: InvalidFormatDescription::InvalidModifier {
value: (*modifier.key).to_owned(),
index: modifier.key.location.byte as usize,
}
});
}
Ok(parsed)
}
};
}
#[inline]
pub(super) fn ident_eq<const VERSION: u8>(provided: &str, expected: &str) -> bool {
assert_version!();
if version!(3..) {
provided == expected
} else {
provided.len() == expected.len()
&& core::iter::zip(provided.bytes(), expected.bytes())
.all(|(p, e)| p.to_ascii_lowercase() == e)
}
}
pub(super) fn parse_optional_format_modifier<const VERSION: u8>(
modifiers: &[Modifier<'_>],
) -> Result<Spanned<bool>, Error> {
let modifiers = parse_modifiers!(VERSION, modifiers, struct {
format: OptionalFormat,
})?;
Ok(modifiers.format.transpose().map(|val| val.unwrap_or(true)))
}
/// Declare the `Component` struct.
macro_rules! component_definition {
(@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
(@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
(@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
(@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
(@if_year "year" $($then:tt)*) => { $($then)* };
(@if_year $lit:tt $($then:tt)*) => {};
($vis:vis enum $name:ident {$(
$variant:ident = $parse_variant:tt {$(
$(#[$required:tt])?
$field:ident = $parse_field:literal:
Option<$(#[$from_str:tt])? $field_type:ty>
),* $(,)?}
),* $(,)?}) => {
$vis enum $name {
$($variant($variant),)*
}
$($vis struct $variant {
$($field: Spanned<Option<$field_type>>),*
})*
$(impl $variant {
/// Parse the component from the AST, given its modifiers.
#[inline]
fn with_modifiers<const VERSION: u8>(
modifiers: &[Modifier<'_>],
_component_span: Span,
) -> Result<Self, Error>
{
assert_version!();
// rustc will complain if the modifier is empty.
#[allow(unused_mut)]
let mut this = Self {
$($field: None.spanned(Span::DUMMY)),*
};
for modifier in modifiers {
$(if ident_eq::<VERSION>(&modifier.key, $parse_field) {
if version!(3..) && this.$field.is_some() {
hint::cold_path();
return Err(Error {
_inner: unused(modifier.key_span().error("duplicate modifier key")),
public: InvalidFormatDescription::DuplicateModifier {
name: stringify!($field),
index: modifier.key.location.byte as usize,
}
});
}
this.$field = Some(
component_definition!(@if_from_str $($from_str)?
then {
parse_from_modifier_value::<$field_type>(
|| modifier.value_span(),
modifier.value,
)?
} else {
<$field_type>::from_modifier_value::<VERSION>(
|| modifier.value_span(),
modifier.value,
)?
}
)
).spanned(modifier.key_value_span());
continue;
})*
hint::cold_path();
return Err(Error {
_inner: unused(modifier.key_span().error("invalid modifier key")),
public: InvalidFormatDescription::InvalidModifier {
value: (*modifier.key).to_owned(),
index: modifier.key.location.byte as usize,
}
});
}
$(component_definition! { @if_required $($required)? then {
if this.$field.is_none() {
hint::cold_path();
return Err(Error {
_inner: unused(_component_span.error("missing required modifier")),
public:
InvalidFormatDescription::MissingRequiredModifier {
name: $parse_field,
index: _component_span.start.byte as usize,
}
});
}
}})*
Ok(this)
}
})*
/// Parse a component from the AST, given its name and modifiers.
#[inline]
pub(super) fn component_from_ast<const VERSION: u8>(
name: &Spanned<&str>,
modifiers: &[Modifier<'_>],
) -> Result<AstComponent, Error> {
assert_version!();
$(if ident_eq::<VERSION>(&name, $parse_variant) {
#[allow(unused_mut)] // only used for some variants
let mut component = AstComponent::$variant(
try_likely_ok!($variant::with_modifiers::<VERSION>(&modifiers, name.span))
);
component_definition!(@if_year $parse_variant
#[cfg(feature = "large-dates")]
if version!(3..)
&& let AstComponent::Year(y) = &mut component
&& y.range.value.is_none()
{
y.range = Some(YearRange::Standard).spanned(Span::DUMMY);
}
);
return Ok(component);
})*
hint::cold_path();
Err(Error {
_inner: unused(name.span.error("invalid component")),
public: InvalidFormatDescription::InvalidComponentName {
name: (**name).to_owned(),
index: name.span.start.byte as usize,
},
})
}
}
}
// Keep in alphabetical order.
component_definition! {
pub(super) enum AstComponent {
Day = "day" {
padding = "padding": Option<Padding>,
},
End = "end" {
trailing_input = "trailing_input": Option<TrailingInput>,
},
Hour = "hour" {
padding = "padding": Option<Padding>,
base = "repr": Option<HourBase>,
},
Ignore = "ignore" {
#[required]
count = "count": Option<#[from_str] NonZero<u16>>,
},
Minute = "minute" {
padding = "padding": Option<Padding>,
},
Month = "month" {
padding = "padding": Option<Padding>,
repr = "repr": Option<MonthRepr>,
case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
},
OffsetHour = "offset_hour" {
sign_behavior = "sign": Option<SignBehavior>,
padding = "padding": Option<Padding>,
},
OffsetMinute = "offset_minute" {
padding = "padding": Option<Padding>,
},
OffsetSecond = "offset_second" {
padding = "padding": Option<Padding>,
},
Ordinal = "ordinal" {
padding = "padding": Option<Padding>,
},
Period = "period" {
case = "case": Option<PeriodCase>,
case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
},
Second = "second" {
padding = "padding": Option<Padding>,
},
Subsecond = "subsecond" {
digits = "digits": Option<SubsecondDigits>,
},
UnixTimestamp = "unix_timestamp" {
precision = "precision": Option<UnixTimestampPrecision>,
sign_behavior = "sign": Option<SignBehavior>,
},
Weekday = "weekday" {
repr = "repr": Option<WeekdayRepr>,
one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
},
WeekNumber = "week_number" {
padding = "padding": Option<Padding>,
repr = "repr": Option<WeekNumberRepr>,
},
Year = "year" {
padding = "padding": Option<Padding>,
repr = "repr": Option<YearRepr>,
range = "range": Option<YearRange>,
base = "base": Option<YearBase>,
sign_behavior = "sign": Option<SignBehavior>,
},
}
}
macro_rules! impl_from_ast_component_for {
($([$reject_nonsensical:literal] $ty:ty),+ $(,)?) => {$(
impl TryFrom<AstComponent> for $ty {
type Error = Error;
#[inline]
fn try_from(component: AstComponent) -> Result<Self, Self::Error> {
macro_rules! reject_modifier {
($modifier:ident, $modifier_str:literal, $context:literal) => {
if $reject_nonsensical && $modifier.value.is_some() {
hint::cold_path();
return Err(Error {
_inner: unused($modifier.span.error(concat!(
"the '",
$modifier_str,
"' modifier is not valid ",
$context
))),
public: InvalidFormatDescription::InvalidModifierCombination {
modifier: $modifier_str,
context: $context,
index: $modifier.span.start.byte as usize,
},
});
}
};
}
use crate::format_description::modifier;
Ok(match component {
AstComponent::Day(Day { padding }) => Self::Day(modifier::Day {
padding: padding.unwrap_or_default().into(),
}),
AstComponent::End(End { trailing_input }) => Self::End(modifier::End {
trailing_input: trailing_input.unwrap_or_default().into(),
}),
AstComponent::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
HourBase::Twelve => Self::Hour12(modifier::Hour12 {
padding: padding.unwrap_or_default().into(),
}),
HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
padding: padding.unwrap_or_default().into(),
}),
},
AstComponent::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
count: match *count {
Some(value) => value,
None => bug!("required modifier was not set"),
},
}),
AstComponent::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
padding: padding.unwrap_or_default().into(),
}),
AstComponent::Month(Month {
padding,
repr,
case_sensitive,
}) => match repr.unwrap_or_default() {
MonthRepr::Numerical => {
reject_modifier!(
case_sensitive,
"case_sensitive",
"for numerical month"
);
Self::MonthNumerical(modifier::MonthNumerical {
padding: padding.unwrap_or_default().into(),
})
},
MonthRepr::Long => {
reject_modifier!(padding, "padding", "for long month");
Self::MonthLong(modifier::MonthLong {
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
},
MonthRepr::Short => {
reject_modifier!(padding, "padding", "for short month");
Self::MonthShort(modifier::MonthShort {
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
},
},
AstComponent::OffsetHour(OffsetHour {
sign_behavior,
padding,
}) => Self::OffsetHour(modifier::OffsetHour {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
padding: padding.unwrap_or_default().into(),
}),
AstComponent::OffsetMinute(OffsetMinute { padding }) => {
Self::OffsetMinute(modifier::OffsetMinute {
padding: padding.unwrap_or_default().into(),
})
}
AstComponent::OffsetSecond(OffsetSecond { padding }) => {
Self::OffsetSecond(modifier::OffsetSecond {
padding: padding.unwrap_or_default().into(),
})
}
AstComponent::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
padding: padding.unwrap_or_default().into(),
}),
AstComponent::Period(Period {
case,
case_sensitive,
}) => Self::Period(modifier::Period {
is_uppercase: case.unwrap_or_default().into(),
case_sensitive: case_sensitive.unwrap_or_default().into(),
}),
AstComponent::Second(Second { padding }) => Self::Second(modifier::Second {
padding: padding.unwrap_or_default().into(),
}),
AstComponent::Subsecond(Subsecond { digits }) => {
Self::Subsecond(modifier::Subsecond {
digits: digits.unwrap_or_default().into(),
})
},
AstComponent::UnixTimestamp(UnixTimestamp {
precision,
sign_behavior,
}) => match precision.unwrap_or_default() {
UnixTimestampPrecision::Second => {
Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
UnixTimestampPrecision::Millisecond => {
Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
UnixTimestampPrecision::Microsecond => {
Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
UnixTimestampPrecision::Nanosecond => {
Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
},
AstComponent::Weekday(Weekday {
repr,
one_indexed,
case_sensitive,
}) => match repr.unwrap_or_default() {
WeekdayRepr::Short => {
reject_modifier!(one_indexed, "one_indexed", "for short weekday");
Self::WeekdayShort(modifier::WeekdayShort {
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
},
WeekdayRepr::Long => {
reject_modifier!(one_indexed, "one_indexed", "for long weekday");
Self::WeekdayLong(modifier::WeekdayLong {
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
},
WeekdayRepr::Sunday => {
reject_modifier!(
case_sensitive,
"case_sensitive",
"for numerical weekday"
);
Self::WeekdaySunday(modifier::WeekdaySunday {
one_indexed: one_indexed.unwrap_or_default().into(),
})
},
WeekdayRepr::Monday => {
reject_modifier!(
case_sensitive,
"case_sensitive",
"for numerical weekday"
);
Self::WeekdayMonday(modifier::WeekdayMonday {
one_indexed: one_indexed.unwrap_or_default().into(),
})
},
},
AstComponent::WeekNumber(WeekNumber { padding, repr }) => {
match repr.unwrap_or_default() {
WeekNumberRepr::Iso => {
Self::WeekNumberIso(modifier::WeekNumberIso {
padding: padding.unwrap_or_default().into(),
})
},
WeekNumberRepr::Sunday => {
Self::WeekNumberSunday(modifier::WeekNumberSunday {
padding: padding.unwrap_or_default().into(),
})
},
WeekNumberRepr::Monday => {
Self::WeekNumberMonday(modifier::WeekNumberMonday {
padding: padding.unwrap_or_default().into(),
})
},
}
}
AstComponent::Year(Year {
padding,
repr,
range,
base,
sign_behavior,
}) => {
#[cfg(not(feature = "large-dates"))]
reject_modifier!(
range,
"range",
"when the `large-dates` feature is not enabled"
);
match (
base.unwrap_or_default(),
repr.unwrap_or_default(),
range.unwrap_or_default(),
) {
#[cfg(feature = "large-dates")]
(YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
Self::CalendarYearFullExtendedRange(
modifier::CalendarYearFullExtendedRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
(YearBase::Calendar, YearRepr::Full, _) => {
Self::CalendarYearFullStandardRange(
modifier::CalendarYearFullStandardRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
#[cfg(feature = "large-dates")]
(YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
Self::CalendarYearCenturyExtendedRange(
modifier::CalendarYearCenturyExtendedRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
(YearBase::Calendar, YearRepr::Century, _) => {
Self::CalendarYearCenturyStandardRange(
modifier::CalendarYearCenturyStandardRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
#[cfg(feature = "large-dates")]
(YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
(YearBase::IsoWeek, YearRepr::Full, _) => {
Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
#[cfg(feature = "large-dates")]
(YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
Self::IsoYearCenturyExtendedRange(
modifier::IsoYearCenturyExtendedRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
(YearBase::IsoWeek, YearRepr::Century, _) => {
Self::IsoYearCenturyStandardRange(
modifier::IsoYearCenturyStandardRange {
padding: padding.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
},
)
}
(YearBase::Calendar, YearRepr::LastTwo, _) => {
#[cfg(feature = "large-dates")]
reject_modifier!(range, "range", "when `repr:last_two` is used");
reject_modifier!(
sign_behavior,
"sign",
"when `repr:last_two` is used"
);
Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
padding: padding.unwrap_or_default().into(),
})
}
(YearBase::IsoWeek, YearRepr::LastTwo, _) => {
#[cfg(feature = "large-dates")]
reject_modifier!(range, "range", "when `repr:last_two` is used");
reject_modifier!(
sign_behavior,
"sign",
"when `repr:last_two` is used"
);
Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
padding: padding.unwrap_or_default().into(),
})
}
}
}
})
}
})+
}
}
impl_from_ast_component_for!(
[false] crate::format_description::Component,
[true] FormatDescriptionV3Inner<'_>,
);
/// Get the target type for a given enum.
macro_rules! target_ty {
($name:ident $type:ty) => {
$type
};
($name:ident) => {
$crate::format_description::modifier::$name
};
}
/// Get the target value for a given enum.
macro_rules! target_value {
($name:ident $variant:ident $value:expr) => {
$value
};
($name:ident $variant:ident) => {
$crate::format_description::modifier::$name::$variant
};
}
trait ModifierValue {
type Type;
}
/// Declare the various modifiers.
///
/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
/// variant. The only significant change is that the string representation of the variant must be
/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
/// used when parsing the modifier. The value is not case sensitive.
///
/// If the type in the public API does not have the same name as the type in the internal
/// representation, then the former must be specified in parenthesis after the internal name. For
/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
/// the public API.
///
/// By default, the internal variant name is assumed to be the same as the public variant name. If
/// this is not the case, the qualified path to the variant must be specified in parenthesis after
/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
/// but is represented as `true` in the public API.
macro_rules! modifier {
($(
$(#[expect($expect_inner:meta)])?
enum $name:ident $(($target_ty:ty))? {
$(
$(#[$attr:meta])?
$variant:ident $(($target_value:expr))? = $parse_variant:literal
),* $(,)?
}
)+) => {$(
#[derive(Default, Clone, Copy)]
enum $name {
$($(#[$attr])? $variant),*
}
impl $name {
/// Parse the modifier from its string representation.
#[inline]
fn from_modifier_value<const VERSION: u8>(
value_span: impl FnOnce() -> Span,
value: &str,
) -> Result<Self, Error>
{
assert_version!();
$(if ident_eq::<VERSION>(&value, $parse_variant) {
return Ok(Self::$variant);
})*
hint::cold_path();
let span = value_span();
Err(Error {
_inner: unused(span.error("invalid modifier value")),
public: InvalidFormatDescription::InvalidModifier {
value: value.to_owned(),
index: span.start.byte as usize,
},
})
}
}
$(#[expect($expect_inner)])?
impl ModifierValue for $name {
type Type = target_ty!($name $($target_ty)?);
}
$(#[expect($expect_inner)])?
impl From<$name> for target_ty!($name $($target_ty)?) {
#[inline]
fn from(modifier: $name) -> Self {
match modifier {
$($name::$variant => target_value!($name $variant $($target_value)?)),*
}
}
}
)+};
}
// Keep in alphabetical order.
modifier! {
enum HourBase(bool) {
Twelve(true) = "12",
#[default]
TwentyFour(false) = "24",
}
enum MonthCaseSensitive(bool) {
False(false) = "false",
#[default]
True(true) = "true",
}
#[expect(deprecated)]
enum MonthRepr {
#[default]
Numerical = "numerical",
Long = "long",
Short = "short",
}
enum OptionalFormat(bool) {
False(false) = "false",
#[default]
True(true) = "true",
}
enum Padding {
Space = "space",
#[default]
Zero = "zero",
None = "none",
}
enum PeriodCase(bool) {
Lower(false) = "lower",
#[default]
Upper(true) = "upper",
}
enum PeriodCaseSensitive(bool) {
False(false) = "false",
#[default]
True(true) = "true",
}
enum SignBehavior(bool) {
#[default]
Automatic(false) = "automatic",
Mandatory(true) = "mandatory",
}
enum SubsecondDigits {
One = "1",
Two = "2",
Three = "3",
Four = "4",
Five = "5",
Six = "6",
Seven = "7",
Eight = "8",
Nine = "9",
#[default]
OneOrMore = "1+",
}
enum TrailingInput {
#[default]
Prohibit = "prohibit",
Discard = "discard",
}
#[expect(deprecated)]
enum UnixTimestampPrecision {
#[default]
Second = "second",
Millisecond = "millisecond",
Microsecond = "microsecond",
Nanosecond = "nanosecond",
}
#[expect(deprecated)]
enum WeekNumberRepr {
#[default]
Iso = "iso",
Sunday = "sunday",
Monday = "monday",
}
enum WeekdayCaseSensitive(bool) {
False(false) = "false",
#[default]
True(true) = "true",
}
enum WeekdayOneIndexed(bool) {
False(false) = "false",
#[default]
True(true) = "true",
}
#[expect(deprecated)]
enum WeekdayRepr {
Short = "short",
#[default]
Long = "long",
Sunday = "sunday",
Monday = "monday",
}
enum YearBase(bool) {
#[default]
Calendar(false) = "calendar",
IsoWeek(true) = "iso_week",
}
#[expect(deprecated)]
enum YearRepr {
#[default]
Full = "full",
Century = "century",
LastTwo = "last_two",
}
// For v1 and v2 format descriptions, the default is `extended`. For v3 format descriptions,
// the default is `standard`. For backwards compatibility, the default here needs to stay
// `extended`.
#[expect(deprecated)]
enum YearRange {
Standard = "standard",
#[default]
Extended = "extended",
}
}
/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
#[inline]
fn parse_from_modifier_value<T>(value_span: impl FnOnce() -> Span, value: &str) -> Result<T, Error>
where
T: FromStr,
{
value.parse::<T>().map_err(|_| {
hint::cold_path();
let span = value_span();
Error {
_inner: unused(span.error("invalid modifier value")),
public: InvalidFormatDescription::InvalidModifier {
value: value.to_owned(),
index: span.start.byte as usize,
},
}
})
}
@@ -0,0 +1,991 @@
//! Lexer for parsing format descriptions.
use alloc::borrow::ToOwned as _;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use super::format_item::{
AstComponent, component_from_ast, ident_eq, parse_optional_format_modifier,
};
use super::{
Error, Location, Span, Spanned, SpannedValue, WithLocation, WithLocationValue as _, unused,
};
use crate::error::InvalidFormatDescription;
use crate::format_description::__private::FormatDescriptionV3Inner;
use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, OwnedFormatItem};
use crate::hint;
use crate::internal_macros::try_likely_ok;
#[must_use]
enum Context {
Component,
Literal,
}
impl Context {
#[inline]
const fn is_component(&self) -> bool {
matches!(self, Self::Component)
}
#[inline]
const fn is_literal(&self) -> bool {
matches!(self, Self::Literal)
}
}
enum NextModifier<'a> {
Modifier(Modifier<'a>),
TrailingWhitespace(Spanned<&'a str>),
None,
}
type ParseItemWithLiteralLifetime<'input, const VERSION: u8, const OWNED: bool> =
<() as ParseTarget<'input, VERSION, OWNED>>::ItemWithLiteralLifetime;
type ParseOutput<'input, const VERSION: u8, const OWNED: bool> =
<() as ParseTarget<'input, VERSION, OWNED>>::Output;
pub(super) trait ParseTarget<'input, const VERSION: u8, const OWNED: bool> {
type ItemWithLiteralLifetime;
type ItemWithStaticLifetime;
type Component: TryFrom<AstComponent, Error: Into<Error>>;
type Output;
fn literal(value: &'input str) -> Self::ItemWithLiteralLifetime;
fn component(component: Self::Component) -> Result<Self::ItemWithLiteralLifetime, Error>;
fn optional(
value: Vec<Self::ItemWithLiteralLifetime>,
format: bool,
span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error>;
fn first(
value: Vec<Vec<Self::ItemWithLiteralLifetime>>,
span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error>;
fn parse(s: &'input str) -> Result<Self::Output, Error>;
}
pub(super) fn parse_generic<'input, const VERSION: u8, const OWNED: bool>(
s: &'input str,
) -> Result<ParseOutput<'input, VERSION, OWNED>, Error>
where
(): ParseTarget<'input, VERSION, OWNED>,
{
<() as ParseTarget<'input, VERSION, OWNED>>::parse(s)
}
macro_rules! v1_v2_parse_target {
($($version:literal)+) => {$(
impl<'input> ParseTarget<'input, $version, false> for () {
type ItemWithLiteralLifetime = BorrowedFormatItem<'input>;
type ItemWithStaticLifetime = BorrowedFormatItem<'static>;
type Component = AstComponent;
type Output = Vec<BorrowedFormatItem<'input>>;
#[inline]
fn literal(value: &'input str) -> Self::ItemWithLiteralLifetime {
BorrowedFormatItem::StringLiteral(value)
}
#[inline]
fn component(component: Self::Component) -> Result<Self::ItemWithStaticLifetime, Error>
{
Ok(BorrowedFormatItem::Component(try_likely_ok!(
component.try_into()
)))
}
#[inline]
fn optional(
_value: Vec<Self::ItemWithLiteralLifetime>,
_format: bool,
span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error> {
hint::cold_path();
Err(Error {
_inner: unused(span.error(
"optional items are not supported in runtime-parsed format descriptions",
)),
public: InvalidFormatDescription::NotSupported {
what: "optional item",
context: "runtime-parsed format descriptions",
index: span.start.byte as usize,
},
})
}
#[inline]
fn first(_value: Vec<Vec<Self::ItemWithLiteralLifetime>>, span: Span)
-> Result<Self::ItemWithLiteralLifetime, Error>
{
hint::cold_path();
Err(Error {
_inner: unused(span.error(
"'first' items are not supported in runtime-parsed format descriptions",
)),
public: InvalidFormatDescription::NotSupported {
what: "'first' item",
context: "runtime-parsed format descriptions",
index: span.start.byte as usize,
},
})
}
#[inline]
fn parse(s: &'input str) -> Result<ParseOutput<'input, $version, false>, Error> {
let mut items = Vec::with_capacity(16);
let mut lexer = Lexer::<$version, false>::new(s);
while !lexer.input.is_empty() {
items.push(try_likely_ok!(lexer.parse_next_item()));
}
Ok(items)
}
}
impl<'input> ParseTarget<'input, $version, true> for () {
type ItemWithLiteralLifetime = OwnedFormatItem;
type ItemWithStaticLifetime = OwnedFormatItem;
type Component = AstComponent;
type Output = OwnedFormatItem;
#[inline]
fn literal(value: &'input str) -> Self::ItemWithLiteralLifetime {
OwnedFormatItem::StringLiteral(value.to_owned().into_boxed_str())
}
#[inline]
fn component(component: Self::Component) -> Result<Self::ItemWithStaticLifetime, Error>
{
Ok(OwnedFormatItem::Component(try_likely_ok!(
component.try_into()
)))
}
#[inline]
fn optional(
value: Vec<Self::ItemWithLiteralLifetime>,
format: bool,
span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error> {
if !format {
hint::cold_path();
return Err(Error {
_inner: unused(span.error(
"v1 and v2 format descriptions do not support optional items that are \
not formatted",
)),
public: InvalidFormatDescription::NotSupported {
what: "optional item with `format:false`",
context: "v1 and v2 format descriptions",
index: span.start.byte as usize,
},
});
}
Ok(OwnedFormatItem::Optional(Box::new(
items_to_owned_format_item(value),
)))
}
#[inline]
fn first(value: Vec<Vec<Self::ItemWithLiteralLifetime>>, _span: Span)
-> Result<Self::ItemWithLiteralLifetime, Error>
{
Ok(OwnedFormatItem::First(
value.into_iter().map(items_to_owned_format_item).collect(),
))
}
#[inline]
fn parse(s: &'input str) -> Result<ParseOutput<'input, $version, true>, Error> {
let mut items = Vec::with_capacity(16);
let mut lexer = Lexer::<$version, true>::new(s);
while !lexer.input.is_empty() {
items.push(try_likely_ok!(lexer.parse_next_item()));
}
Ok(items_to_owned_format_item(items))
}
}
)+};
}
macro_rules! v3_parse_target {
($owned:tt, $output_lt:lifetime, $literal:expr, $items_to_v3:expr) => {
impl<'input> ParseTarget<'input, 3, $owned> for () {
type ItemWithLiteralLifetime = FormatDescriptionV3Inner<$output_lt>;
type ItemWithStaticLifetime = FormatDescriptionV3Inner<'static>;
type Component = FormatDescriptionV3Inner<'static>;
type Output = FormatDescriptionV3<$output_lt>;
#[inline]
fn literal(value: &'input str) -> Self::ItemWithLiteralLifetime {
$literal(value.into())
}
#[inline]
fn component(
component: Self::Component,
) -> Result<Self::ItemWithStaticLifetime, Error> {
Ok(component)
}
#[inline]
fn optional(
value: Vec<Self::ItemWithLiteralLifetime>,
format: bool,
_span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error> {
Ok(FormatDescriptionV3Inner::OwnedOptional {
format,
item: Box::new($items_to_v3(value)),
})
}
#[inline]
fn first(
value: Vec<Vec<Self::ItemWithLiteralLifetime>>,
_span: Span,
) -> Result<Self::ItemWithLiteralLifetime, Error> {
Ok(FormatDescriptionV3Inner::OwnedFirst(
value.into_iter().map($items_to_v3).collect(),
))
}
#[inline]
fn parse(s: &'input str) -> Result<Self::Output, Error> {
let mut items = Vec::with_capacity(16);
let mut lexer = Lexer::<3, false>::new(s);
while let Some(&byte) = lexer.input.first() {
let location = Location {
byte: lexer.byte_pos,
};
let token = match byte {
b'[' => lexer.consume_component(location),
b']' => {
hint::cold_path();
return Err(Error {
_inner: unused(location.error("right brackets must be escaped")),
public: InvalidFormatDescription::Expected {
what: "right bracket to be escaped",
index: location.byte as usize,
},
});
}
b'\\' => lexer
.consume_backslash_escape_sequence(location)
.map(<() as ParseTarget<'input, 3, $owned>>::literal),
_ => Ok(<() as ParseTarget<'input, 3, $owned>>::literal(
lexer.consume_literal().into(),
)),
};
items.push(try_likely_ok!(token));
}
Ok($items_to_v3(items).into_opaque())
}
}
};
}
v1_v2_parse_target!(1 2);
v3_parse_target!(false, 'input, FormatDescriptionV3Inner::BorrowedLiteral, items_to_v3_borrowed);
v3_parse_target!(true, 'static, FormatDescriptionV3Inner::OwnedLiteral, items_to_v3_owned);
fn items_to_owned_format_item(items: Vec<OwnedFormatItem>) -> OwnedFormatItem {
match <[_; 1]>::try_from(items) {
Ok([item]) => item,
Err(items) => OwnedFormatItem::Compound(items.into_boxed_slice()),
}
}
fn items_to_v3_borrowed<'input>(
items: Vec<FormatDescriptionV3Inner<'input>>,
) -> FormatDescriptionV3Inner<'input> {
match <[_; 1]>::try_from(items) {
Ok([item]) => item,
Err(items) => FormatDescriptionV3Inner::OwnedCompound(items.into_boxed_slice()),
}
}
fn items_to_v3_owned(
items: Vec<FormatDescriptionV3Inner<'_>>,
) -> FormatDescriptionV3Inner<'static> {
match <[_; 1]>::try_from(items) {
Ok([item]) => item.into_owned(),
Err(items) => FormatDescriptionV3Inner::OwnedCompound(
items
.into_iter()
.map(FormatDescriptionV3Inner::into_owned)
.collect(),
),
}
}
/// An iterator over the lexed tokens.
pub(super) struct Lexer<'input, const VERSION: u8, const OWNED: bool> {
input: &'input [u8],
depth: u8,
byte_pos: u32,
}
impl<'input, const VERSION: u8, const OWNED: bool> Lexer<'input, VERSION, OWNED> {
/// Parse the string into a series of [`Token`]s.
///
/// `VERSION` controls the version of the format description that is being parsed.
///
/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`. For the
/// start of a nested format description, a single `[` is used and is _never_ part of the
/// escape sequence. For example, `[optional [[day]]]` will lex successfully, ultimately
/// resulting in a component named `optional` with the nested component `day`.
/// - When `VERSION` is 2 or 3, all escape sequences begin with `\`. The only characters that
/// may currently follow are `\`, `[`, and `]`, all of which result in the literal character.
/// All other characters result in a lex error.
#[inline]
pub(super) const fn new(input: &'input str) -> Self {
Self {
input: input.as_bytes(),
depth: 0,
byte_pos: 0,
}
}
/// Advance the input by the given number of bytes.
#[inline]
fn advance(&mut self, bytes: u32) {
self.input = &self.input[bytes as usize..];
self.byte_pos += bytes;
}
/// Whether the lexer is currently parsing a component or a literal.
#[inline]
const fn context(&self) -> Context {
if self.depth.is_multiple_of(2) {
Context::Literal
} else {
Context::Component
}
}
/// Consume the next token if it is a component item that is whitespace.
#[inline]
fn consume_whitespace(&mut self) -> Option<Spanned<&'input str>> {
debug_assert!(self.context().is_component());
let bytes = self
.input
.iter()
.take_while(|byte| byte.is_ascii_whitespace())
.count() as u32;
if bytes == 0 {
return None;
}
let start_loc = Location {
byte: self.byte_pos,
};
let end_loc = Location {
byte: self.byte_pos + bytes,
};
// Safety: Runtime format descriptions always originate with a string passed as a parameter
// and we have only consumed full codepoints, ensuring that a valid string remains.
let value = unsafe { str::from_utf8_unchecked(&self.input[..bytes as usize]) };
self.advance(bytes);
Some(value.spanned(start_loc.to(end_loc)))
}
/// Consume the next token if it is a component item that is not whitespace.
#[inline]
fn consume_component_part(&mut self) -> Option<Spanned<&'input str>> {
debug_assert!(self.context().is_component());
let bytes = self
.input
.iter()
.take_while(|byte| !byte.is_ascii_whitespace() && !matches!(byte, b'\\' | b'[' | b']'))
.count() as u32;
if bytes == 0 {
hint::cold_path();
return None;
}
let start_loc = Location {
byte: self.byte_pos,
};
let end_loc = Location {
byte: self.byte_pos + bytes,
};
// Safety: Runtime format descriptions always originate with a string passed as a parameter
// and we have only consumed full codepoints, ensuring that a valid string remains.
let value = unsafe { str::from_utf8_unchecked(&self.input[..bytes as usize]) };
self.advance(bytes);
Some(value.spanned(start_loc.to(end_loc)))
}
/// Consume the next token if it is a closing bracket.
#[inline]
fn consume_closing_bracket(&mut self) -> Option<Location> {
if self.input.first() != Some(&b']') {
hint::cold_path();
return None;
}
self.depth -= 1;
let location = Location {
byte: self.byte_pos,
};
self.advance(1);
Some(location)
}
/// Consume the next token if it is a component name. The caller is expected to be inside a
/// component header.
#[inline]
fn consume_component_name(
&mut self,
opening_bracket: Location,
) -> Result<Spanned<&'input str>, Error> {
let leading_whitespace = self.consume_whitespace().is_some();
let Some(name) = self.consume_component_part() else {
hint::cold_path();
let location = if leading_whitespace {
opening_bracket.offset(1)
} else {
opening_bracket
};
return Err(Error {
_inner: unused(location.error("expected component name")),
public: InvalidFormatDescription::MissingComponentName {
index: location.byte as usize,
},
});
};
Ok(name)
}
#[inline]
fn consume_modifier(&mut self) -> Result<NextModifier<'input>, Error> {
let Some(whitespace) = self.consume_whitespace() else {
hint::cold_path();
return Ok(NextModifier::None);
};
let Some(token) = self.consume_component_part() else {
hint::cold_path();
return Ok(NextModifier::TrailingWhitespace(whitespace));
};
let modifier = try_likely_ok!(self.modifier_from_token(token));
Ok(NextModifier::Modifier(modifier))
}
/// Parse a component.
#[inline]
fn consume_component(
&mut self,
opening_bracket: Location,
) -> Result<ParseItemWithLiteralLifetime<'input, VERSION, OWNED>, Error>
where
(): ParseTarget<'input, VERSION, OWNED>,
{
match self.depth.checked_add(1) {
Some(depth) => self.depth = depth,
None => {
hint::cold_path();
return Err(Error {
_inner: unused(opening_bracket.error("too much nesting")),
public: InvalidFormatDescription::NotSupported {
what: "highly-nested format description",
context: "",
index: opening_bracket.byte as usize,
},
});
}
};
// consume the opening bracket, which was checked prior to calling this method
self.advance(1);
let name = try_likely_ok!(self.consume_component_name(opening_bracket));
let modifiers = try_likely_ok!(Modifiers::parse::<VERSION, OWNED>(self));
let mut nested_format_descriptions = Vec::new();
while self.is_nested_description_start()
&& let Ok(description) = self.consume_nested(modifiers.end_location())
{
nested_format_descriptions.push(description);
}
if modifiers.trailing_whitespace.is_some()
&& let Some(first_nested) = nested_format_descriptions.first_mut()
{
first_nested.leading_whitespace = modifiers.trailing_whitespace;
}
if modifiers.trailing_whitespace.is_none() || !nested_format_descriptions.is_empty() {
self.consume_whitespace();
}
let Some(closing_bracket) = self.consume_closing_bracket() else {
hint::cold_path();
return Err(Error {
_inner: unused(opening_bracket.error("unclosed bracket")),
public: InvalidFormatDescription::UnclosedOpeningBracket {
index: opening_bracket.byte as usize,
},
});
};
if let Some(first_nested_fd) = nested_format_descriptions.first()
&& first_nested_fd.leading_whitespace.is_none()
{
hint::cold_path();
return Err(Error {
_inner: unused(
opening_bracket
.to(closing_bracket)
.error("missing leading whitespace before nested format description"),
),
public: InvalidFormatDescription::Expected {
what: "whitespace before nested format description",
index: first_nested_fd.opening_bracket.byte as usize,
},
});
}
if ident_eq::<VERSION>(*name, "optional") {
hint::cold_path();
let format = try_likely_ok!(parse_optional_format_modifier::<VERSION>(
&modifiers.modifiers,
));
let nested_format_description = match <[_; 1]>::try_from(nested_format_descriptions) {
Ok([nested_format_description]) => nested_format_description,
Err(e) => {
hint::cold_path();
if let Some((second_fd, last_fd)) = e.first().zip(e.last()) {
return Err(Error {
_inner: unused(
second_fd.opening_bracket.to(last_fd.closing_bracket).error(
"the `optional` component only allows a single nested format \
description",
),
),
public: InvalidFormatDescription::NotSupported {
what: "more than one nested format description",
context: "`optional` components",
index: second_fd.opening_bracket.byte as usize,
},
});
} else {
return Err(Error {
_inner: unused(opening_bracket.to(closing_bracket).error(
"missing nested format description for `optional` component",
)),
public: InvalidFormatDescription::Expected {
what: "nested format description",
index: closing_bracket.byte as usize,
},
});
}
}
};
return <() as ParseTarget<'input, VERSION, OWNED>>::optional(
nested_format_description.items,
*format,
opening_bracket.to(closing_bracket),
);
}
if ident_eq::<VERSION>(*name, "first") {
hint::cold_path();
if !modifiers.modifiers.is_empty() {
hint::cold_path();
let modifier = &modifiers.modifiers[0];
return Err(Error {
_inner: unused(modifier.key_span().error("invalid modifier key")),
public: InvalidFormatDescription::InvalidModifier {
value: (*modifier.key).to_owned(),
index: modifier.key.location.byte as usize,
},
});
}
if version!(3..) && nested_format_descriptions.is_empty() {
hint::cold_path();
return Err(Error {
_inner: unused(opening_bracket.to(closing_bracket).error(
"the `first` component requires at least one nested format description",
)),
public: InvalidFormatDescription::Expected {
what: "at least one nested format description",
index: closing_bracket.byte as usize,
},
});
}
let items = nested_format_descriptions
.into_iter()
.map(|nested_format_description| nested_format_description.items)
.collect();
return <() as ParseTarget<'input, VERSION, OWNED>>::first(
items,
opening_bracket.to(closing_bracket),
);
}
if !nested_format_descriptions.is_empty() {
hint::cold_path();
return Err(Error {
_inner: unused(
opening_bracket
.to(closing_bracket)
.error("this component does not support nested format descriptions"),
),
public: InvalidFormatDescription::NotSupported {
what: "nested format descriptions",
context: "on this component",
index: opening_bracket.byte as usize,
},
});
}
let component = try_likely_ok!(component_from_ast::<VERSION>(&name, &modifiers.modifiers));
<() as ParseTarget<'input, VERSION, OWNED>>::component(try_likely_ok!(component.try_into()))
}
/// Parse a nested format description. The location provided is the most recent one consumed.
#[inline]
fn consume_nested(
&mut self,
last_location: Location,
) -> Result<
NestedFormatDescription<'input, ParseItemWithLiteralLifetime<'input, VERSION, OWNED>>,
Error,
>
where
(): ParseTarget<'input, VERSION, OWNED>,
{
let leading_whitespace = self.consume_whitespace();
let opening_bracket = {
match self.depth.checked_add(1) {
Some(depth) => self.depth = depth,
None => {
hint::cold_path();
return Err(Error {
_inner: unused(last_location.error("too much nesting")),
public: InvalidFormatDescription::NotSupported {
what: "highly-nested format description",
context: "",
index: last_location.byte as usize,
},
});
}
}
let location = Location {
byte: self.byte_pos,
};
self.advance(1);
location
};
let mut items = Vec::new();
while !self.input.is_empty() {
// If we're in a literal context and the next byte is a closing bracket, stop so that we
// can consume it.
if self.context().is_literal() && self.input.first() == Some(&b']') {
break;
}
items.push(try_likely_ok!(self.parse_next_item()));
}
let Some(closing_bracket) = self.consume_closing_bracket() else {
hint::cold_path();
// The opening bracket consumed above incremented `self.depth` but was never matched by
// a closing bracket. `consume_component` swallows this error and then re-derives the
// outer "unclosed bracket" error itself, so we must leave `depth` (and therefore
// `context()`) exactly as it was on entry; otherwise the subsequent
// `debug_assert!(self.context().is_component())` calls (e.g. in `consume_whitespace`)
// are tripped on inputs such as `[a[` or `[hour[`.
self.depth -= 1;
return Err(Error {
_inner: unused(opening_bracket.error("unclosed bracket")),
public: InvalidFormatDescription::UnclosedOpeningBracket {
index: opening_bracket.byte as usize,
},
});
};
Ok(NestedFormatDescription {
leading_whitespace,
opening_bracket,
items,
closing_bracket,
})
}
#[inline]
fn modifier_from_token(&self, token: Spanned<&'input str>) -> Result<Modifier<'input>, Error> {
let Some(colon_index) = token.bytes().position(|b| b == b':') else {
hint::cold_path();
return Err(Error {
_inner: unused(token.span.error("modifier must be of the form `key:value`")),
public: InvalidFormatDescription::InvalidModifier {
value: (*token).to_owned(),
index: token.span.start.byte as usize,
},
});
};
let key = &token[..colon_index];
let value = &token[colon_index + 1..];
if key.is_empty() {
hint::cold_path();
return Err(Error {
_inner: unused(token.span.shrink_to_start().error("expected modifier key")),
public: InvalidFormatDescription::InvalidModifier {
value: String::new(),
index: token.span.start.byte as usize,
},
});
}
if value.is_empty() {
hint::cold_path();
return Err(Error {
_inner: unused(token.span.shrink_to_end().error("expected modifier value")),
public: InvalidFormatDescription::InvalidModifier {
value: String::new(),
index: token.span.start.byte as usize + colon_index,
},
});
}
Ok(Modifier {
key: key.with_location(token.span.start),
value,
})
}
/// Check whether the next tokens start a nested format description. Does not consume any
/// input.
///
/// Note that this call is strictly an optimization, as checking the error path on
/// `parse_nested` is sufficient for knowing if a nested format description is present. This
/// method avoids the overhead of constructing an error only to throw it away.
#[inline]
fn is_nested_description_start(&self) -> bool {
debug_assert!(self.context().is_component());
let Some(index) = self
.input
.iter()
.position(|&byte| !byte.is_ascii_whitespace())
else {
return false;
};
self.input[index] == b'['
&& (version!(2..)
|| self.context().is_component()
|| self.input.get(index + 1) != Some(&b'['))
}
#[inline]
fn consume_literal(&mut self) -> &'input str {
let bytes = self
.input
.iter()
.take_while(|&&byte| byte != b'[' && byte != b']' && (version!(1) || byte != b'\\'))
.count() as u32;
// Safety: A string was passed to this function, and only UTF-8 has been consumed,
// leaving behind a string known to begin at a character boundary.
let value = unsafe { str::from_utf8_unchecked(&self.input[..bytes as usize]) };
self.advance(bytes);
value
}
#[inline]
fn consume_backslash_escape_sequence(
&mut self,
location: Location,
) -> Result<&'input str, Error> {
let backslash_loc = location;
Ok(match self.input.get(1) {
Some(b'\\' | b'[' | b']') => {
// The escaped character is emitted as-is.
// Safety: We know that this is either a left bracket, right bracket, or
// backslash.
let char = unsafe { str::from_utf8_unchecked(&self.input[1..2]) };
self.advance(2);
char
}
Some(_) => {
hint::cold_path();
let loc = Location {
byte: self.byte_pos + 1,
};
return Err(Error {
_inner: unused(loc.error("invalid escape sequence")),
public: InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: loc.byte as usize,
},
});
}
None => {
hint::cold_path();
return Err(Error {
_inner: unused(backslash_loc.error("unexpected end of input")),
public: InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: backslash_loc.byte as usize,
},
});
}
})
}
}
impl<'input, const VERSION: u8, const OWNED: bool> Lexer<'input, VERSION, OWNED> {
#[inline(always)]
fn parse_next_item(
&mut self,
) -> Result<ParseItemWithLiteralLifetime<'input, VERSION, OWNED>, Error>
where
(): ParseTarget<'input, VERSION, OWNED>,
{
let byte = self.input[0];
let location = Location {
byte: self.byte_pos,
};
Ok(match byte {
b'[' if version!(1) && self.input.get(1) == Some(&b'[') => {
self.advance(2);
<() as ParseTarget<'input, VERSION, OWNED>>::literal("[")
}
b'[' => return self.consume_component(location),
b']' if version!(3..) => {
hint::cold_path();
return Err(Error {
_inner: unused(location.error("right brackets must be escaped")),
public: InvalidFormatDescription::Expected {
what: "right bracket to be escaped",
index: location.byte as usize,
},
});
}
b']' if version!(1..=2) => {
self.advance(1);
<() as ParseTarget<'input, VERSION, OWNED>>::literal("]")
}
b'\\' if version!(2..) => {
return self
.consume_backslash_escape_sequence(location)
.map(<() as ParseTarget<'input, VERSION, OWNED>>::literal);
}
_ => <() as ParseTarget<'input, VERSION, OWNED>>::literal(self.consume_literal()),
})
}
}
/// A format description that is nested within another format description.
pub(super) struct NestedFormatDescription<'a, Item> {
/// Whitespace between the end of the previous item and the opening bracket.
pub(super) leading_whitespace: Option<Spanned<&'a str>>,
/// Where the opening bracket was in the format string.
pub(super) opening_bracket: Location,
/// The items within the nested format description.
pub(super) items: Vec<Item>,
/// Where the closing bracket was in the format string.
pub(super) closing_bracket: Location,
}
/// A modifier for a component.
pub(super) struct Modifier<'a> {
/// The key of the modifier.
pub(super) key: WithLocation<&'a str>,
/// The value of the modifier.
pub(super) value: &'a str,
}
impl Modifier<'_> {
#[inline]
pub(super) fn key_value_span(&self) -> Span {
self.key
.location
.with_length(self.key.len() + self.value.len() + 1)
}
#[inline]
pub(super) fn key_span(&self) -> Span {
self.key.location.with_length(self.key.len())
}
#[inline]
pub(super) fn value_span(&self) -> Span {
self.key
.location
.offset(self.key.len() as u32 + 1)
.with_length(self.value.len())
}
}
pub(super) struct Modifiers<'a> {
pub(super) modifiers: Vec<Modifier<'a>>,
pub(super) trailing_whitespace: Option<Spanned<&'a str>>,
}
impl<'a> Modifiers<'a> {
/// Parse modifiers until there are none left. Returns the modifiers along with any trailing
/// whitespace after the last modifier.
#[inline]
pub(super) fn parse<const VERSION: u8, const OWNED: bool>(
tokens: &mut Lexer<'a, VERSION, OWNED>,
) -> Result<Self, Error> {
let mut modifiers = Vec::new();
loop {
match try_likely_ok!(tokens.consume_modifier()) {
NextModifier::Modifier(modifier) => modifiers.push(modifier),
NextModifier::TrailingWhitespace(whitespace) => {
return Ok(Self {
modifiers,
trailing_whitespace: Some(whitespace),
});
}
NextModifier::None => {
return Ok(Self {
modifiers,
trailing_whitespace: None,
});
}
}
}
}
#[inline]
pub(super) fn end_location(&self) -> Location {
match &*self.modifiers {
[] => Location::DUMMY,
[.., modifier] => modifier.value_span().end,
}
}
}
@@ -0,0 +1,422 @@
//! Parser for format descriptions.
use alloc::vec::Vec;
use self::lexer_ast::parse_generic;
use self::sealed::{Version, VersionedParser};
pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned};
use crate::error;
use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, OwnedFormatItem};
macro_rules! version {
($pat:pat) => {
const { matches!(VERSION, $pat) }
};
}
macro_rules! assert_version {
() => {
const {
assert!(matches!(VERSION, 1..=3), "invalid version provided");
}
};
}
mod format_item;
mod lexer_ast;
mod strftime;
mod sealed {
use super::*;
/// The version of the parser, represented in the type system.
#[expect(
missing_debug_implementations,
reason = "only used at the type level; not public API"
)]
pub struct Version<const N: usize>;
/// A trait for parsing format descriptions, with different output types depending on the
/// version.
pub trait VersionedParser {
/// The output type of the borrowed parser. This type avoids allocating where possible.
type BorrowedOutput<'input>;
/// The output type of the owned parser. This type may allocate but is valid for `'static`.
type OwnedOutput;
/// Parse a format description into a type that avoids allocating where possible.
fn parse_borrowed(
s: &str,
) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription>;
/// Parse a format description into an owned type, which may allocate but is valid for
/// `'static`.
fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription>;
}
}
impl VersionedParser for Version<1> {
type BorrowedOutput<'input> = Vec<BorrowedFormatItem<'input>>;
type OwnedOutput = OwnedFormatItem;
#[inline]
fn parse_borrowed(
s: &str,
) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
Ok(parse_generic::<1, false>(s)?)
}
#[inline]
fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
Ok(parse_generic::<1, true>(s)?)
}
}
impl VersionedParser for Version<2> {
type BorrowedOutput<'input> = Vec<BorrowedFormatItem<'input>>;
type OwnedOutput = OwnedFormatItem;
#[inline]
fn parse_borrowed(
s: &str,
) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
Ok(parse_generic::<2, false>(s)?)
}
#[inline]
fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
Ok(parse_generic::<2, true>(s)?)
}
}
impl VersionedParser for Version<3> {
type BorrowedOutput<'input> = FormatDescriptionV3<'input>;
type OwnedOutput = FormatDescriptionV3<'static>;
#[inline]
fn parse_borrowed(
s: &str,
) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
Ok(parse_generic::<3, false>(s)?)
}
#[inline]
fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
Ok(parse_generic::<3, true>(s)?)
}
}
/// Parse a sequence of items from the format description.
///
/// The syntax for the format description can be found in [the
/// book](https://time-rs.github.io/book/api/format-description.html).
///
/// This function exists for backward compatibility reasons. It is equivalent to calling
/// `parse_borrowed::<1>(s)`. **It is recommended to use version 3, not version 1.**
#[deprecated(
since = "0.3.48",
note = "use `parse_borrowed` with the appropriate version for clarity"
)]
#[inline]
pub fn parse(s: &str) -> Result<Vec<BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
parse_borrowed::<1>(s)
}
/// Parse a sequence of items from the format description.
///
/// The syntax for the format description can be found in [the
/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
/// description is provided as the const parameter. **It is recommended to use version 3.**
///
/// # Return type
///
/// The return type of this function depends on the version provided.
///
/// - For versions 1 and 2, the function returns `Result<Vec<BorrowedFormatItem<'_>>,
/// InvalidFormatDescription>`.
/// - For version 3, the function returns `Result<FormatDescriptionV3<'_>,
/// InvalidFormatDescription>`.
#[inline]
pub fn parse_borrowed<const VERSION: usize>(
s: &str,
) -> Result<
<Version<VERSION> as VersionedParser>::BorrowedOutput<'_>,
error::InvalidFormatDescription,
>
where
Version<VERSION>: VersionedParser,
{
Version::<VERSION>::parse_borrowed(s)
}
/// Parse a sequence of items from the format description.
///
/// The syntax for the format description can be found in [the
/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
/// description is provided as the const parameter.
///
/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
/// that there is no lifetime that needs to be handled. **It is recommended to use version 3.**
///
/// # Return type
///
/// The return type of this function depends on the version provided.
///
/// - For versions 1 and 2, the function returns `Result<OwnedFormatItem,
/// InvalidFormatDescription>`.
/// - For version 3, the function returns `Result<FormatDescriptionV3<'static>,
/// InvalidFormatDescription>`.
///
/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
#[inline]
pub fn parse_owned<const VERSION: usize>(
s: &str,
) -> Result<<Version<VERSION> as VersionedParser>::OwnedOutput, error::InvalidFormatDescription>
where
Version<VERSION>: VersionedParser,
{
Version::<VERSION>::parse_owned(s)
}
/// A location within a string.
#[derive(Clone, Copy)]
struct Location {
/// The zero-indexed byte of the string.
byte: u32,
}
impl Location {
const DUMMY: Self = Self { byte: u32::MAX };
/// Create a new [`Span`] from `self` to `other`.
#[inline]
const fn to(self, end: Self) -> Span {
Span { start: self, end }
}
/// Create a new [`Span`] consisting entirely of `self`.
#[inline]
const fn to_self(self) -> Span {
Span {
start: self,
end: self,
}
}
#[inline]
const fn with_length(self, length: usize) -> Span {
Span {
start: self,
end: Self {
byte: self.byte + length as u32 - 1,
},
}
}
/// Offset the location by the provided amount.
///
/// Note that this assumes the resulting location is on the same line as the original location.
#[must_use = "this does not modify the original value"]
#[inline]
const fn offset(&self, offset: u32) -> Self {
Self {
byte: self.byte + offset,
}
}
/// Create an error with the provided message at this location.
#[inline]
const fn error(self, message: &'static str) -> ErrorInner {
ErrorInner {
_message: message,
_span: Span {
start: self,
end: self,
},
}
}
}
/// A value with an associated [`Location`].
#[derive(Clone, Copy)]
struct WithLocation<T> {
/// The value.
value: T,
/// Where the value was in the format string.
location: Location,
}
impl<T> core::ops::Deref for WithLocation<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.value
}
}
/// Helper trait to attach a [`Location`] to a value.
trait WithLocationValue: Sized {
/// Attach a [`Location`] to a value.
fn with_location(self, location: Location) -> WithLocation<Self>;
}
impl<T> WithLocationValue for T {
#[inline]
fn with_location(self, location: Location) -> WithLocation<Self> {
WithLocation {
value: self,
location,
}
}
}
/// A start and end point within a string.
#[derive(Clone, Copy)]
struct Span {
start: Location,
end: Location,
}
impl Span {
const DUMMY: Self = Self {
start: Location { byte: u32::MAX },
end: Location { byte: u32::MAX },
};
/// Obtain a `Span` pointing at the start of the pre-existing span.
#[must_use = "this does not modify the original value"]
#[inline]
const fn shrink_to_start(&self) -> Self {
Self {
start: self.start,
end: self.start,
}
}
/// Obtain a `Span` pointing at the end of the pre-existing span.
#[must_use = "this does not modify the original value"]
const fn shrink_to_end(&self) -> Self {
Self {
start: self.end,
end: self.end,
}
}
/// Create an error with the provided message at this span.
#[inline]
const fn error(self, message: &'static str) -> ErrorInner {
ErrorInner {
_message: message,
_span: self,
}
}
}
/// A value with an associated [`Span`].
#[derive(Clone, Copy)]
struct Spanned<T> {
/// The value.
value: T,
/// Where the value was in the format string.
span: Span,
}
impl<T> core::ops::Deref for Spanned<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> Spanned<T> {
#[inline]
fn map<F, U>(self, f: F) -> Spanned<U>
where
F: FnOnce(T) -> U,
{
Spanned {
value: f(self.value),
span: self.span,
}
}
}
trait OptionExt<T> {
fn transpose(self) -> Spanned<Option<T>>;
}
impl<T> OptionExt<T> for Option<Spanned<T>> {
#[inline]
fn transpose(self) -> Spanned<Option<T>> {
match self {
Some(spanned) => Spanned {
value: Some(spanned.value),
span: spanned.span,
},
None => Spanned {
value: None,
span: Span::DUMMY,
},
}
}
}
/// Helper trait to attach a [`Span`] to a value.
trait SpannedValue: Sized {
/// Attach a [`Span`] to a value.
fn spanned(self, span: Span) -> Spanned<Self>;
}
impl<T> SpannedValue for T {
#[inline]
fn spanned(self, span: Span) -> Spanned<Self> {
Spanned { value: self, span }
}
}
/// The internal error type.
struct ErrorInner {
/// The message displayed to the user.
_message: &'static str,
/// Where the error originated.
_span: Span,
}
/// A complete error description.
struct Error {
/// The internal error.
_inner: Unused<ErrorInner>,
/// The error needed for interoperability with the rest of `time`.
public: error::InvalidFormatDescription,
}
impl From<Error> for error::InvalidFormatDescription {
#[inline]
fn from(error: Error) -> Self {
error.public
}
}
impl From<core::convert::Infallible> for Error {
#[inline]
fn from(v: core::convert::Infallible) -> Self {
match v {}
}
}
/// A value that may be used in the future, but currently is not.
///
/// This struct exists so that data can semantically be passed around without _actually_ passing it
/// around. This way the data still exists if it is needed in the future.
// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
struct Unused<T>(core::marker::PhantomData<T>);
/// Indicate that a value is currently unused.
#[inline]
fn unused<T>(_: T) -> Unused<T> {
Unused(core::marker::PhantomData)
}
@@ -0,0 +1,441 @@
use alloc::string::String;
use alloc::vec::Vec;
use crate::error::InvalidFormatDescription;
use crate::format_description::modifier::Padding;
use crate::format_description::parse::{
Error, ErrorInner, Location, Spanned, SpannedValue, unused,
};
use crate::format_description::{BorrowedFormatItem, Component, OwnedFormatItem, modifier};
use crate::internal_macros::try_likely_ok;
/// Parse a sequence of items from the [`strftime` format description][strftime docs].
///
/// The only heap allocation required is for the `Vec` itself. All components are bound to the
/// lifetime of the input.
///
/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html
#[doc(alias = "parse_strptime_borrowed")]
#[inline]
pub fn parse_strftime_borrowed(
s: &str,
) -> Result<Vec<BorrowedFormatItem<'_>>, InvalidFormatDescription> {
let mut items = Vec::with_capacity(s.bytes().filter(|&b| b == b'%').count().saturating_add(2));
for item in Tokenizer::new(s.as_bytes()) {
items.push(try_likely_ok!(item));
}
Ok(items)
}
/// Parse a sequence of items from the [`strftime` format description][strftime docs].
///
/// This requires heap allocation for some owned items.
///
/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html
#[doc(alias = "parse_strptime_owned")]
#[inline]
pub fn parse_strftime_owned(s: &str) -> Result<OwnedFormatItem, InvalidFormatDescription> {
parse_strftime_borrowed(s).map(Into::into)
}
struct Tokenizer<'input> {
input: &'input [u8],
byte_pos: u32,
}
impl Tokenizer<'_> {
#[inline]
const fn new(input: &[u8]) -> Tokenizer<'_> {
Tokenizer { input, byte_pos: 0 }
}
}
impl<'input> Iterator for Tokenizer<'input> {
type Item = Result<BorrowedFormatItem<'input>, Error>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.input.is_empty() {
return None;
}
if self.input[0] != b'%' {
let bytes = self
.input
.iter()
.position(|&b| b == b'%')
.unwrap_or(self.input.len()) as u32;
// Safety: `parse_strftime` functions only accept strings and only UTF-8 is consumed, so
// UTF-8 validation is unnecessary.
let value = unsafe { str::from_utf8_unchecked(&self.input[..bytes as usize]) };
self.input = &self.input[bytes as usize..];
self.byte_pos += bytes;
return Some(Ok(BorrowedFormatItem::StringLiteral(value)));
}
let padding = match self.input.get(1) {
Some(&b'_') => Some(Padding::Space),
Some(&b'-') => Some(Padding::None),
Some(&b'0') => Some(Padding::Zero),
Some(_) => None,
None => {
return Some(Err(error_expected_end(Location {
byte: self.byte_pos,
})));
}
};
let (component, advance) = match (padding, self.input.get(2)) {
(Some(_), Some(&component)) => (component, 3),
(Some(_), None) => {
return Some(Err(error_expected_end(Location {
byte: self.byte_pos + 2,
})));
}
(None, _) => (self.input[1], 2),
};
let component_loc = Location {
byte: self.byte_pos + (advance - 1) as u32,
};
self.input = &self.input[advance..];
self.byte_pos += advance as u32;
Some(parse_component(
padding,
component.spanned(component_loc.to_self()),
))
}
}
#[cold]
fn error_expected_end(location: Location) -> Error {
Error {
_inner: unused(location.error("unexpected end of input")),
public: InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: location.byte as usize,
},
}
}
#[cold]
fn error_unsupported_modifier(component: Spanned<u8>) -> Error {
Error {
_inner: unused(ErrorInner {
_message: "unsupported modifier",
_span: component.span,
}),
public: InvalidFormatDescription::NotSupported {
what: "modifier",
context: "",
index: component.span.start.byte as usize,
},
}
}
#[cold]
fn error_unsupported_component(component: Spanned<u8>) -> Error {
Error {
_inner: unused(ErrorInner {
_message: "unsupported component",
_span: component.span,
}),
public: InvalidFormatDescription::NotSupported {
what: "component",
context: "",
index: component.span.start.byte as usize,
},
}
}
#[cold]
fn error_invalid_component(component: Spanned<u8>) -> Error {
let name = if component.is_ascii() {
// Safety: The byte is a single ASCII character, which is guaranteed to be valid
// UTF-8.
unsafe { String::from_utf8_unchecked(Vec::from([*component])) }
} else {
String::from(char::REPLACEMENT_CHARACTER)
};
Error {
_inner: unused(ErrorInner {
_message: "invalid component",
_span: component.span,
}),
public: InvalidFormatDescription::InvalidComponentName {
name,
index: component.span.start.byte as usize,
},
}
}
#[inline]
fn parse_component(
padding: Option<Padding>,
component: Spanned<u8>,
) -> Result<BorrowedFormatItem<'static>, Error> {
/// Helper macro to create a component.
macro_rules! component {
($name:ident { $($inner:tt)* }) => {
BorrowedFormatItem::Component(Component::$name(modifier::$name {
$($inner)*
}))
}
}
Ok(match *component {
b'%' => BorrowedFormatItem::StringLiteral("%"),
b'a' => component!(WeekdayShort {
case_sensitive: true
}),
b'A' => component!(WeekdayLong {
case_sensitive: true,
}),
b'b' | b'h' => component!(MonthShort {
case_sensitive: true,
}),
b'B' => component!(MonthLong {
case_sensitive: true,
}),
b'c' => BorrowedFormatItem::Compound(&[
component!(WeekdayShort {
case_sensitive: true,
}),
BorrowedFormatItem::StringLiteral(" "),
component!(MonthShort {
case_sensitive: true,
}),
BorrowedFormatItem::StringLiteral(" "),
component!(Day {
padding: Padding::Space
}),
BorrowedFormatItem::StringLiteral(" "),
component!(Hour24 {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Minute {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Second {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(" "),
#[cfg(feature = "large-dates")]
component!(CalendarYearFullExtendedRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
#[cfg(not(feature = "large-dates"))]
component!(CalendarYearFullStandardRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
]),
#[cfg(feature = "large-dates")]
b'C' => component!(CalendarYearCenturyExtendedRange {
padding: padding.unwrap_or(Padding::Zero),
sign_is_mandatory: false,
}),
#[cfg(not(feature = "large-dates"))]
b'C' => component!(CalendarYearCenturyStandardRange {
padding: padding.unwrap_or(Padding::Zero),
sign_is_mandatory: false,
}),
b'd' => component!(Day {
padding: padding.unwrap_or(Padding::Zero),
}),
b'D' => BorrowedFormatItem::Compound(&[
component!(MonthNumerical {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral("/"),
component!(Day {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral("/"),
component!(CalendarYearLastTwo {
padding: Padding::Zero,
}),
]),
b'e' => component!(Day {
padding: padding.unwrap_or(Padding::Space),
}),
b'F' => BorrowedFormatItem::Compound(&[
#[cfg(feature = "large-dates")]
component!(CalendarYearFullExtendedRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
#[cfg(not(feature = "large-dates"))]
component!(CalendarYearFullStandardRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
BorrowedFormatItem::StringLiteral("-"),
component!(MonthNumerical {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral("-"),
component!(Day {
padding: Padding::Zero,
}),
]),
b'g' => component!(IsoYearLastTwo {
padding: padding.unwrap_or(Padding::Zero),
}),
#[cfg(feature = "large-dates")]
b'G' => component!(IsoYearFullExtendedRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
#[cfg(not(feature = "large-dates"))]
b'G' => component!(IsoYearFullStandardRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
b'H' => component!(Hour24 {
padding: padding.unwrap_or(Padding::Zero),
}),
b'I' => component!(Hour12 {
padding: padding.unwrap_or(Padding::Zero),
}),
b'j' => component!(Ordinal {
padding: padding.unwrap_or(Padding::Zero),
}),
b'k' => component!(Hour24 {
padding: padding.unwrap_or(Padding::Space),
}),
b'l' => component!(Hour12 {
padding: padding.unwrap_or(Padding::Space),
}),
b'm' => component!(MonthNumerical {
padding: padding.unwrap_or(Padding::Zero),
}),
b'M' => component!(Minute {
padding: padding.unwrap_or(Padding::Zero),
}),
b'n' => BorrowedFormatItem::StringLiteral("\n"),
b'O' => return Err(error_unsupported_modifier(component)),
b'p' => component!(Period {
is_uppercase: true,
case_sensitive: true
}),
b'P' => component!(Period {
is_uppercase: false,
case_sensitive: true
}),
b'r' => BorrowedFormatItem::Compound(&[
component!(Hour12 {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Minute {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Second {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(" "),
component!(Period {
is_uppercase: true,
case_sensitive: true,
}),
]),
b'R' => BorrowedFormatItem::Compound(&[
component!(Hour24 {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Minute {
padding: Padding::Zero,
}),
]),
b's' => component!(UnixTimestampSecond {
sign_is_mandatory: false,
}),
b'S' => component!(Second {
padding: padding.unwrap_or(Padding::Zero),
}),
b't' => BorrowedFormatItem::StringLiteral("\t"),
b'T' => BorrowedFormatItem::Compound(&[
component!(Hour24 {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Minute {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Second {
padding: Padding::Zero,
}),
]),
b'u' => component!(WeekdayMonday { one_indexed: true }),
b'U' => component!(WeekNumberSunday {
padding: padding.unwrap_or(Padding::Zero),
}),
b'V' => component!(WeekNumberIso {
padding: padding.unwrap_or(Padding::Zero),
}),
b'w' => component!(WeekdaySunday { one_indexed: true }),
b'W' => component!(WeekNumberMonday {
padding: padding.unwrap_or(Padding::Zero),
}),
b'x' => BorrowedFormatItem::Compound(&[
component!(MonthNumerical {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral("/"),
component!(Day {
padding: Padding::Zero
}),
BorrowedFormatItem::StringLiteral("/"),
component!(CalendarYearLastTwo {
padding: Padding::Zero,
}),
]),
b'X' => BorrowedFormatItem::Compound(&[
component!(Hour24 {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Minute {
padding: Padding::Zero,
}),
BorrowedFormatItem::StringLiteral(":"),
component!(Second {
padding: Padding::Zero,
}),
]),
b'y' => component!(CalendarYearLastTwo {
padding: padding.unwrap_or(Padding::Zero),
}),
#[cfg(feature = "large-dates")]
b'Y' => component!(CalendarYearFullExtendedRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
#[cfg(not(feature = "large-dates"))]
b'Y' => component!(CalendarYearFullStandardRange {
padding: Padding::Zero,
sign_is_mandatory: false,
}),
b'z' => BorrowedFormatItem::Compound(&[
component!(OffsetHour {
sign_is_mandatory: true,
padding: Padding::Zero,
}),
component!(OffsetMinute {
padding: Padding::Zero,
}),
]),
b'Z' => return Err(error_unsupported_component(component)),
_ => return Err(error_invalid_component(component)),
})
}
@@ -0,0 +1,264 @@
//! The format described in ISO 8601.
mod adt_hack;
use core::num::NonZero;
#[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).
///
/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions.
///
/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`].
/// Passing any other value is **unspecified behavior**.
///
/// Example: 1997-11-21T09:55:06.000000000-06:00
///
/// # Examples
#[cfg_attr(feature = "formatting", doc = "```rust")]
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
/// # use time::format_description::well_known::Iso8601;
/// # use time_macros::datetime;
/// assert_eq!(
/// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?,
/// "1997-11-12T09:55:06.000000000-06:00"
/// );
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Iso8601<const CONFIG: EncodedConfig = { Config::DEFAULT.encode() }>;
impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Iso8601")
.field("config", &Config::decode(CONFIG))
.finish()
}
}
/// 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:
///
/// - The configuration can be used for both formatting and parsing.
/// - The date, time, and UTC offset are all formatted.
/// - Separators (such as `-` and `:`) are included.
/// - The year contains four digits, such that the year must be between 0 and 9999.
/// - The date uses the calendar format.
/// - 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 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 = 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.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormattedComponents {
/// The configuration can only be used for parsing. Using this to format a value is
/// unspecified behavior.
None,
/// Format only the date.
Date,
/// Format only the time.
Time,
/// Format only the UTC offset.
Offset,
/// Format the date and time.
DateTime,
/// Format the date, time, and UTC offset.
DateTimeOffset,
/// Format the time and UTC offset.
TimeOffset,
}
/// Which format to use for the date.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DateKind {
/// Use the year-month-day format.
Calendar,
/// Use the year-week-weekday format.
Week,
/// Use the week-ordinal format.
Ordinal,
}
/// The precision and number of decimal digits present for the time.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimePrecision {
/// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
/// specified number of decimal digits, if any.
Hour {
#[expect(missing_docs)]
decimal_digits: Option<NonZero<u8>>,
},
/// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
/// number of decimal digits, if any.
Minute {
#[expect(missing_docs)]
decimal_digits: Option<NonZero<u8>>,
},
/// Format the hour, minute, and second. Nanoseconds will be represented with the specified
/// number of decimal digits, if any.
Second {
#[expect(missing_docs)]
decimal_digits: Option<NonZero<u8>>,
},
}
/// The precision for the UTC offset.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OffsetPrecision {
/// Format only the offset hour. Requires the offset minute to be zero.
Hour,
/// Format both the offset hour and minute.
Minute,
}
/// Configuration for [`Iso8601`].
// This is only used as a const generic, so there's no need to have a number of implementations on
// it.
#[expect(missing_copy_implementations, reason = "forwards compatibility")]
#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this.
#[derive(Debug)]
pub struct Config {
/// Which components, if any, will be formatted.
pub(crate) formatted_components: FormattedComponents,
/// Whether the format contains separators (such as `-` or `:`).
pub(crate) use_separators: bool,
/// Whether the year is six digits.
pub(crate) year_is_six_digits: bool,
/// The format used for the date.
pub(crate) date_kind: DateKind,
/// The precision and number of decimal digits present for the time.
pub(crate) time_precision: TimePrecision,
/// The precision for the UTC offset.
pub(crate) offset_precision: OffsetPrecision,
}
impl Config {
/// A configuration for the [`Iso8601`] format.
///
/// The following is the default behavior:
///
/// - The configuration can be used for both formatting and parsing.
/// - The date, time, and UTC offset are all formatted.
/// - Separators (such as `-` and `:`) are included.
/// - The year contains four digits, such that the year must be between 0 and 9999.
/// - The date uses the calendar format.
/// - 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 the setter methods on this struct.
pub const DEFAULT: Self = Self {
formatted_components: FormattedComponents::DateTimeOffset,
use_separators: true,
year_is_six_digits: false,
date_kind: DateKind::Calendar,
time_precision: TimePrecision::Second {
decimal_digits: NonZero::new(9),
},
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.
#[inline]
pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
Self {
formatted_components,
..self
}
}
/// Set whether the format contains separators (such as `-` or `:`).
#[inline]
pub const fn set_use_separators(self, use_separators: bool) -> Self {
Self {
use_separators,
..self
}
}
/// Set whether the year is six digits.
#[inline]
pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self {
Self {
year_is_six_digits,
..self
}
}
/// Set the format used for the date.
#[inline]
pub const fn set_date_kind(self, date_kind: DateKind) -> Self {
Self { date_kind, ..self }
}
/// Set the precision and number of decimal digits present for the time.
#[inline]
pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
Self {
time_precision,
..self
}
}
/// Set the precision for the UTC offset.
#[inline]
pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
Self {
offset_precision,
..self
}
}
}
@@ -0,0 +1,230 @@
//! Hackery to work around not being able to use ADTs in const generics on stable.
use core::num::NonZero;
#[cfg(feature = "formatting")]
use super::Iso8601;
use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision};
// This provides a way to include `EncodedConfig` in documentation without displaying the type it is
// aliased to.
#[doc(hidden)]
pub type DoNotRelyOnWhatThisIs = u128;
/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`](super::Iso8601).
///
/// The type this is aliased to must not be relied upon. It can change in any release without
/// notice.
pub type EncodedConfig = DoNotRelyOnWhatThisIs;
#[cfg(feature = "formatting")]
impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
/// The user-provided configuration for the ISO 8601 format.
const CONFIG: Config = Config::decode(CONFIG);
/// Whether the date should be formatted.
pub(crate) const FORMAT_DATE: bool = matches!(
Self::CONFIG.formatted_components,
FC::Date | FC::DateTime | FC::DateTimeOffset
);
/// Whether the time should be formatted.
pub(crate) const FORMAT_TIME: bool = matches!(
Self::CONFIG.formatted_components,
FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset
);
/// Whether the UTC offset should be formatted.
pub(crate) const FORMAT_OFFSET: bool = matches!(
Self::CONFIG.formatted_components,
FC::Offset | FC::DateTimeOffset | FC::TimeOffset
);
/// Whether the year is six digits.
pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits;
/// Whether the format contains separators (such as `-` or `:`).
pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators;
/// Which format to use for the date.
pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind;
/// The precision and number of decimal digits to use for the time.
pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision;
/// The precision for the UTC offset.
pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision;
}
impl Config {
/// 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`]. Any
/// other usage is unspecified behavior.
pub const fn encode(&self) -> EncodedConfig {
let mut bytes = [0; EncodedConfig::BITS as usize / 8];
bytes[0] = match self.formatted_components {
FC::None => 0,
FC::Date => 1,
FC::Time => 2,
FC::Offset => 3,
FC::DateTime => 4,
FC::DateTimeOffset => 5,
FC::TimeOffset => 6,
};
bytes[1] = self.use_separators as u8;
bytes[2] = self.year_is_six_digits as u8;
bytes[3] = match self.date_kind {
DateKind::Calendar => 0,
DateKind::Week => 1,
DateKind::Ordinal => 2,
};
bytes[4] = match self.time_precision {
TimePrecision::Hour { .. } => 0,
TimePrecision::Minute { .. } => 1,
TimePrecision::Second { .. } => 2,
};
bytes[5] = match self.time_precision {
TimePrecision::Hour { decimal_digits }
| TimePrecision::Minute { decimal_digits }
| TimePrecision::Second { decimal_digits } => match decimal_digits {
None => 0,
Some(decimal_digits) => decimal_digits.get(),
},
};
bytes[6] = match self.offset_precision {
OffsetPrecision::Hour => 0,
OffsetPrecision::Minute => 1,
};
EncodedConfig::from_be_bytes(bytes)
}
/// Decode the configuration. The configuration must have been generated from
/// [`Config::encode`].
pub(super) const fn decode(encoded: EncodedConfig) -> Self {
let bytes = encoded.to_be_bytes();
let formatted_components = match bytes[0] {
0 => FC::None,
1 => FC::Date,
2 => FC::Time,
3 => FC::Offset,
4 => FC::DateTime,
5 => FC::DateTimeOffset,
6 => FC::TimeOffset,
_ => panic!("invalid configuration"),
};
let use_separators = match bytes[1] {
0 => false,
1 => true,
_ => panic!("invalid configuration"),
};
let year_is_six_digits = match bytes[2] {
0 => false,
1 => true,
_ => panic!("invalid configuration"),
};
let date_kind = match bytes[3] {
0 => DateKind::Calendar,
1 => DateKind::Week,
2 => DateKind::Ordinal,
_ => panic!("invalid configuration"),
};
let time_precision = match bytes[4] {
0 => TimePrecision::Hour {
decimal_digits: NonZero::new(bytes[5]),
},
1 => TimePrecision::Minute {
decimal_digits: NonZero::new(bytes[5]),
},
2 => TimePrecision::Second {
decimal_digits: NonZero::new(bytes[5]),
},
_ => panic!("invalid configuration"),
};
let offset_precision = match bytes[6] {
0 => OffsetPrecision::Hour,
1 => OffsetPrecision::Minute,
_ => panic!("invalid configuration"),
};
// No `for` loops in `const fn`.
let mut idx = 7; // first unused byte
while idx < EncodedConfig::BITS as usize / 8 {
if bytes[idx] != 0 {
panic!("invalid configuration");
}
idx += 1;
}
Self {
formatted_components,
use_separators,
year_is_six_digits,
date_kind,
time_precision,
offset_precision,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(Config::DEFAULT)]
#[case(Config::DEFAULT.set_formatted_components(FC::None))]
#[case(Config::DEFAULT.set_formatted_components(FC::Date))]
#[case(Config::DEFAULT.set_formatted_components(FC::Time))]
#[case(Config::DEFAULT.set_formatted_components(FC::Offset))]
#[case(Config::DEFAULT.set_formatted_components(FC::DateTime))]
#[case(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset))]
#[case(Config::DEFAULT.set_formatted_components(FC::TimeOffset))]
#[case(Config::DEFAULT.set_use_separators(false))]
#[case(Config::DEFAULT.set_use_separators(true))]
#[case(Config::DEFAULT.set_year_is_six_digits(false))]
#[case(Config::DEFAULT.set_year_is_six_digits(true))]
#[case(Config::DEFAULT.set_date_kind(DateKind::Calendar))]
#[case(Config::DEFAULT.set_date_kind(DateKind::Week))]
#[case(Config::DEFAULT.set_date_kind(DateKind::Ordinal))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
decimal_digits: None
}))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
decimal_digits: None
}))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Second {
decimal_digits: None
}))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
decimal_digits: NonZero::new(1)
}))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
decimal_digits: NonZero::new(1)
}))]
#[case(Config::DEFAULT.set_time_precision(TimePrecision::Second {
decimal_digits: NonZero::new(1)
}))]
#[case(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour))]
#[case(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute))]
fn encoding_roundtrip(#[case] config: Config) {
let encoded = config.encode();
let decoded = Config::decode(encoded);
assert_eq!(config.formatted_components, decoded.formatted_components);
assert_eq!(config.use_separators, decoded.use_separators);
assert_eq!(config.year_is_six_digits, decoded.year_is_six_digits);
assert_eq!(config.date_kind, decoded.date_kind);
assert_eq!(config.time_precision, decoded.time_precision);
assert_eq!(config.offset_precision, decoded.offset_precision);
}
#[rstest]
#[case(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00)]
#[case(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00)]
#[case(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00)]
#[case(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00)]
#[case(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00)]
#[case(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00)]
#[case(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00)]
#[should_panic]
fn decode_fail(#[case] encoded: EncodedConfig) {
Config::decode(encoded);
}
}
@@ -0,0 +1,30 @@
//! The format described in RFC 2822.
/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3).
///
/// Example: Fri, 21 Nov 1997 09:55:06 -0600
///
/// # Examples
#[cfg_attr(feature = "parsing", doc = "```rust")]
#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
/// # use time::{format_description::well_known::Rfc2822, OffsetDateTime};
/// use time_macros::datetime;
/// assert_eq!(
/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?,
/// datetime!(1993-06-12 13:25:19 +00:00)
/// );
/// # Ok::<_, time::Error>(())
/// ```
///
#[cfg_attr(feature = "formatting", doc = "```rust")]
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
/// # use time::format_description::well_known::Rfc2822;
/// # use time_macros::datetime;
/// assert_eq!(
/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?,
/// "Fri, 21 Nov 1997 09:55:06 -0600"
/// );
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rfc2822;
@@ -0,0 +1,30 @@
//! The format described in RFC 3339.
/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6).
///
/// Format example: 1985-04-12T23:20:50.52Z
///
/// # Examples
#[cfg_attr(feature = "parsing", doc = "```rust")]
#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
/// # use time::{format_description::well_known::Rfc3339, OffsetDateTime};
/// # use time_macros::datetime;
/// assert_eq!(
/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?,
/// datetime!(1985-04-12 23:20:50.52 +00:00)
/// );
/// # Ok::<_, time::Error>(())
/// ```
///
#[cfg_attr(feature = "formatting", doc = "```rust")]
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
/// # use time::format_description::well_known::Rfc3339;
/// # use time_macros::datetime;
/// assert_eq!(
/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?,
/// "1985-04-12T23:20:50.52Z"
/// );
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rfc3339;
@@ -0,0 +1,590 @@
use num_conv::prelude::*;
use crate::format_description::Period;
use crate::formatting::{
Day, IsoWeekNumber, MondayBasedWeek, OptionDay, OptionIsoWeekNumber, OptionYear, Ordinal,
SundayBasedWeek, Year,
};
use crate::time::{Hours, Minutes, Nanoseconds, Seconds};
use crate::utc_offset::{Hours as OffsetHours, Minutes as OffsetMinutes, Seconds as OffsetSeconds};
use crate::{
Date, Month, OffsetDateTime, PlainDateTime, Time, Timestamp, UtcDateTime, UtcOffset, Weekday,
};
/// State used by date-providing types to cache computed values.
///
/// This is used to avoid redundant computations when multiple date components are almost certainly
/// going to be requested within the same formatting invocation.
#[derive(Debug, Default)]
pub(crate) struct DateState {
day: OptionDay,
month: Option<Month>,
iso_week: OptionIsoWeekNumber,
iso_year: OptionYear,
}
/// State used by `Timestamp` to cache computed date and time values.
///
/// `Date` and `Time` are cached separately, with the `Date`'s state being stored to allow for
/// reusing existing methods.
#[derive(Debug, Default)]
pub(crate) struct TimestampState {
date: Option<Date>,
time: Option<Time>,
date_state: <Date as ComponentProvider>::State,
}
macro_rules! unimplemented_methods {
($(
$(#[$meta:meta])*
($component:literal) $name:ident => $ret:ty;
)*) => {
$(
$(#[$meta])*
#[track_caller]
#[expect(unused_variables, reason = "better for auto-generation of method stubs")]
fn $name(&self, state: &mut Self::State) -> $ret {
unimplemented!(concat!("type does not supply ", $component, " components"))
}
)*
};
}
macro_rules! delegate_providers {
(
$target:ident {
$($method:ident -> $return:ty)*
}
) => {$(
#[inline]
fn $method(&self, state: &mut Self::State) -> $return {
ComponentProvider::$method(&self.$target(), state)
}
)*};
(
$target:ident ($state:expr) {
$($method:ident -> $return:ty)*
}
) => {$(
#[inline]
fn $method(&self, _: &mut Self::State) -> $return {
ComponentProvider::$method(&self.$target(), $state)
}
)*};
}
/// A type with the ability to provide date, time, offset, and/or timestamp components on demand.
///
/// Note that while all methods have a default body, implementations are expected to override the
/// body for all components that they provide. The default implementation exists solely for
/// convenience, avoiding the need to specify unprovided components.
pub(crate) trait ComponentProvider {
/// The state type used by the provider, allowing for caching of computed values.
type State: Default;
/// Whether the type can provide date components, indicating that date-related methods can be
/// called.
const SUPPLIES_DATE: bool = false;
/// Whether the type can provide time components, indicating that time-related methods can be
/// called.
const SUPPLIES_TIME: bool = false;
/// Whether the type can provide offset components, indicating that offset-related methods can
/// be called.
const SUPPLIES_OFFSET: bool = false;
/// Whether the type can provide timestamp components, indicating that timestamp-related methods
/// can be called.
const SUPPLIES_TIMESTAMP: bool = false;
unimplemented_methods! {
/// Obtain the day of the month.
("date") day => Day;
/// Obtain the month of the year.
("date") month => Month;
/// Obtain the ordinal day of the year.
("date") ordinal => Ordinal;
/// Obtain the day of the week.
("date") weekday => Weekday;
/// Obtain the ISO week number.
("date") iso_week_number => IsoWeekNumber;
/// Obtain the Monday-based week number.
("date") monday_based_week => MondayBasedWeek;
/// Obtain the Sunday-based week number.
("date") sunday_based_week => SundayBasedWeek;
/// Obtain the calendar year.
("date") calendar_year => Year;
/// Obtain the ISO week-based year.
("date") iso_year => Year;
/// Obtain the hour within the day.
("time") hour => Hours;
/// Obtain the minute within the hour.
("time") minute => Minutes;
/// Obtain the period of the day (AM/PM).
("time") period => Period;
/// Obtain the second within the minute.
("time") second => Seconds;
/// Obtain the nanosecond within the second.
("time") nanosecond => Nanoseconds;
/// Obtain whether the offset is negative.
("offset") offset_is_negative => bool;
/// Obtain whether the offset is UTC.
("offset") offset_is_utc => bool;
/// Obtain the hour component of the UTC offset.
("offset") offset_hour => OffsetHours;
/// Obtain the minute component of the UTC offset.
("offset") offset_minute => OffsetMinutes;
/// Obtain the second component of the UTC offset.
("offset") offset_second => OffsetSeconds;
/// Obtain the Unix timestamp in seconds.
("timestamp") unix_timestamp_seconds => i64;
/// Obtain the Unix timestamp in milliseconds.
("timestamp") unix_timestamp_milliseconds => i64;
/// Obtain the Unix timestamp in microseconds.
("timestamp") unix_timestamp_microseconds => i128;
/// Obtain the Unix timestamp in nanoseconds.
("timestamp") unix_timestamp_nanoseconds => i128;
}
}
impl ComponentProvider for Time {
type State = ();
const SUPPLIES_TIME: bool = true;
#[inline]
fn hour(&self, _: &mut Self::State) -> Hours {
self.as_hms_nano_ranged().0
}
#[inline]
fn minute(&self, _: &mut Self::State) -> Minutes {
self.as_hms_nano_ranged().1
}
#[inline]
fn period(&self, _: &mut Self::State) -> Period {
if (*self).hour() < 12 {
Period::Am
} else {
Period::Pm
}
}
#[inline]
fn second(&self, _: &mut Self::State) -> Seconds {
self.as_hms_nano_ranged().2
}
#[inline]
fn nanosecond(&self, _: &mut Self::State) -> Nanoseconds {
self.as_hms_nano_ranged().3
}
}
impl ComponentProvider for Date {
type State = DateState;
const SUPPLIES_DATE: bool = true;
#[inline]
fn day(&self, state: &mut Self::State) -> Day {
if let Some(day) = state.day.get() {
return day;
}
let (_, month, day) = (*self).to_calendar_date();
// Safety: `day` is guaranteed to be in range.
let day = unsafe { Day::new_unchecked(day) };
state.month = Some(month);
state.day = OptionDay::Some(day);
day
}
#[inline]
fn month(&self, state: &mut Self::State) -> Month {
*state.month.get_or_insert_with(|| (*self).month())
}
#[inline]
fn ordinal(&self, _: &mut Self::State) -> Ordinal {
// Safety: `self.ordinal()` is guaranteed to be in range.
unsafe { Ordinal::new_unchecked((*self).ordinal()) }
}
#[inline]
fn weekday(&self, _: &mut Self::State) -> Weekday {
(*self).weekday()
}
#[inline]
fn iso_week_number(&self, state: &mut Self::State) -> IsoWeekNumber {
if let Some(week) = state.iso_week.get() {
return week;
}
let (iso_year, iso_week) = (*self).iso_year_week();
// Safety: `iso_week` is guaranteed to be non-zero.
let iso_week = unsafe { IsoWeekNumber::new_unchecked(iso_week) };
// Safety: `iso_year` is guaranteed to be in range.
state.iso_year = OptionYear::Some(unsafe { Year::new_unchecked(iso_year) });
state.iso_week = OptionIsoWeekNumber::Some(iso_week);
iso_week
}
#[inline]
fn monday_based_week(&self, _: &mut Self::State) -> MondayBasedWeek {
// Safety: `self.monday_based_week()` is guaranteed to be in range.
unsafe { MondayBasedWeek::new_unchecked((*self).monday_based_week()) }
}
#[inline]
fn sunday_based_week(&self, _: &mut Self::State) -> SundayBasedWeek {
// Safety: `self.sunday_based_week()` is guaranteed to be in range.
unsafe { SundayBasedWeek::new_unchecked((*self).sunday_based_week()) }
}
#[inline]
fn calendar_year(&self, _: &mut Self::State) -> Year {
// Safety: `self.year()` is guaranteed to be in range.
unsafe { Year::new_unchecked((*self).year()) }
}
#[inline]
fn iso_year(&self, state: &mut Self::State) -> Year {
if let Some(iso_year) = state.iso_year.get() {
return iso_year;
}
let (iso_year, iso_week) = (*self).iso_year_week();
// Safety: `iso_year_week` returns a valid ISO year.
let iso_year = unsafe { Year::new_unchecked(iso_year) };
state.iso_year = OptionYear::Some(iso_year);
// Safety: `iso_week` is guaranteed to be non-zero.
state.iso_week =
OptionIsoWeekNumber::Some(unsafe { IsoWeekNumber::new_unchecked(iso_week) });
iso_year
}
}
impl ComponentProvider for PlainDateTime {
type State = DateState;
const SUPPLIES_DATE: bool = true;
const SUPPLIES_TIME: bool = true;
delegate_providers!(date {
day -> Day
month -> Month
ordinal -> Ordinal
weekday -> Weekday
iso_week_number -> IsoWeekNumber
monday_based_week -> MondayBasedWeek
sunday_based_week -> SundayBasedWeek
calendar_year -> Year
iso_year -> Year
});
delegate_providers!(time (&mut ()) {
hour -> Hours
minute -> Minutes
period -> Period
second -> Seconds
nanosecond -> Nanoseconds
});
}
impl ComponentProvider for UtcOffset {
type State = ();
const SUPPLIES_OFFSET: bool = true;
#[inline]
fn offset_is_negative(&self, _: &mut Self::State) -> bool {
(*self).is_negative()
}
#[inline]
fn offset_is_utc(&self, _state: &mut Self::State) -> bool {
(*self).is_utc()
}
#[inline]
fn offset_hour(&self, _: &mut Self::State) -> OffsetHours {
(*self).as_hms_ranged().0
}
#[inline]
fn offset_minute(&self, _: &mut Self::State) -> OffsetMinutes {
(*self).as_hms_ranged().1
}
#[inline]
fn offset_second(&self, _: &mut Self::State) -> OffsetSeconds {
(*self).as_hms_ranged().2
}
}
impl ComponentProvider for UtcDateTime {
type State = DateState;
const SUPPLIES_DATE: bool = true;
const SUPPLIES_TIME: bool = true;
const SUPPLIES_OFFSET: bool = true;
const SUPPLIES_TIMESTAMP: bool = true;
delegate_providers!(date {
day -> Day
month -> Month
ordinal -> Ordinal
weekday -> Weekday
iso_week_number -> IsoWeekNumber
monday_based_week -> MondayBasedWeek
sunday_based_week -> SundayBasedWeek
calendar_year -> Year
iso_year -> Year
});
delegate_providers!(time (&mut ()) {
hour -> Hours
minute -> Minutes
period -> Period
second -> Seconds
nanosecond -> Nanoseconds
});
#[inline]
fn offset_is_negative(&self, _: &mut Self::State) -> bool {
false
}
#[inline]
fn offset_is_utc(&self, _state: &mut Self::State) -> bool {
true
}
#[inline]
fn offset_hour(&self, _: &mut Self::State) -> OffsetHours {
OffsetHours::new_static::<0>()
}
#[inline]
fn offset_minute(&self, _: &mut Self::State) -> OffsetMinutes {
OffsetMinutes::new_static::<0>()
}
#[inline]
fn offset_second(&self, _: &mut Self::State) -> OffsetSeconds {
OffsetSeconds::new_static::<0>()
}
#[inline]
fn unix_timestamp_seconds(&self, _: &mut Self::State) -> i64 {
(*self).unix_timestamp()
}
#[inline]
fn unix_timestamp_milliseconds(&self, state: &mut Self::State) -> i64 {
(ComponentProvider::unix_timestamp_nanoseconds(self, state) / 1_000_000).truncate()
}
#[inline]
fn unix_timestamp_microseconds(&self, state: &mut Self::State) -> i128 {
ComponentProvider::unix_timestamp_nanoseconds(self, state) / 1_000
}
#[inline]
fn unix_timestamp_nanoseconds(&self, _: &mut Self::State) -> i128 {
(*self).unix_timestamp_nanos()
}
}
impl ComponentProvider for OffsetDateTime {
type State = DateState;
const SUPPLIES_DATE: bool = true;
const SUPPLIES_TIME: bool = true;
const SUPPLIES_OFFSET: bool = true;
const SUPPLIES_TIMESTAMP: bool = true;
delegate_providers!(date {
day -> Day
month -> Month
ordinal -> Ordinal
weekday -> Weekday
iso_week_number -> IsoWeekNumber
monday_based_week -> MondayBasedWeek
sunday_based_week -> SundayBasedWeek
calendar_year -> Year
iso_year -> Year
});
delegate_providers!(time (&mut ()) {
hour -> Hours
minute -> Minutes
period -> Period
second -> Seconds
nanosecond -> Nanoseconds
});
delegate_providers!(offset (&mut ()) {
offset_is_negative -> bool
offset_is_utc -> bool
offset_hour -> OffsetHours
offset_minute -> OffsetMinutes
offset_second -> OffsetSeconds
});
#[inline]
fn unix_timestamp_seconds(&self, _: &mut Self::State) -> i64 {
(*self).unix_timestamp()
}
#[inline]
fn unix_timestamp_milliseconds(&self, _: &mut Self::State) -> i64 {
((*self).unix_timestamp_nanos() / 1_000_000) as i64
}
#[inline]
fn unix_timestamp_microseconds(&self, _: &mut Self::State) -> i128 {
(*self).unix_timestamp_nanos() / 1_000
}
#[inline]
fn unix_timestamp_nanoseconds(&self, _: &mut Self::State) -> i128 {
(*self).unix_timestamp_nanos()
}
}
impl ComponentProvider for Timestamp {
type State = TimestampState;
const SUPPLIES_DATE: bool = true;
const SUPPLIES_TIME: bool = true;
const SUPPLIES_OFFSET: bool = true;
const SUPPLIES_TIMESTAMP: bool = true;
#[inline]
fn day(&self, state: &mut Self::State) -> Day {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::day(date, &mut state.date_state)
}
#[inline]
fn month(&self, state: &mut Self::State) -> Month {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::month(date, &mut state.date_state)
}
#[inline]
fn ordinal(&self, state: &mut Self::State) -> Ordinal {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::ordinal(date, &mut state.date_state)
}
#[inline]
fn weekday(&self, state: &mut Self::State) -> Weekday {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::weekday(date, &mut state.date_state)
}
#[inline]
fn iso_week_number(&self, state: &mut Self::State) -> IsoWeekNumber {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::iso_week_number(date, &mut state.date_state)
}
#[inline]
fn monday_based_week(&self, state: &mut Self::State) -> MondayBasedWeek {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::monday_based_week(date, &mut state.date_state)
}
#[inline]
fn sunday_based_week(&self, state: &mut Self::State) -> SundayBasedWeek {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::sunday_based_week(date, &mut state.date_state)
}
#[inline]
fn calendar_year(&self, state: &mut Self::State) -> Year {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::calendar_year(date, &mut state.date_state)
}
#[inline]
fn iso_year(&self, state: &mut Self::State) -> Year {
let date = state.date.get_or_insert_with(|| self.date());
ComponentProvider::iso_year(date, &mut state.date_state)
}
#[inline]
fn hour(&self, state: &mut Self::State) -> Hours {
let time = state.time.get_or_insert_with(|| self.time());
ComponentProvider::hour(time, &mut ())
}
#[inline]
fn minute(&self, state: &mut Self::State) -> Minutes {
let time = state.time.get_or_insert_with(|| self.time());
ComponentProvider::minute(time, &mut ())
}
#[inline]
fn period(&self, state: &mut Self::State) -> Period {
let time = state.time.get_or_insert_with(|| self.time());
ComponentProvider::period(time, &mut ())
}
#[inline]
fn second(&self, state: &mut Self::State) -> Seconds {
let time = state.time.get_or_insert_with(|| self.time());
ComponentProvider::second(time, &mut ())
}
#[inline]
fn nanosecond(&self, _: &mut Self::State) -> Nanoseconds {
// No need to cache time here, as nanosecond is stored separately in `Timestamp` and can be
// directly accessed.
self.as_parts_ranged().1
}
#[inline]
fn offset_is_negative(&self, _: &mut Self::State) -> bool {
false
}
#[inline]
fn offset_is_utc(&self, _: &mut Self::State) -> bool {
true
}
#[inline]
fn offset_hour(&self, _: &mut Self::State) -> OffsetHours {
OffsetHours::new_static::<0>()
}
#[inline]
fn offset_minute(&self, _: &mut Self::State) -> OffsetMinutes {
OffsetMinutes::new_static::<0>()
}
#[inline]
fn offset_second(&self, _: &mut Self::State) -> OffsetSeconds {
OffsetSeconds::new_static::<0>()
}
#[inline]
fn unix_timestamp_seconds(&self, _: &mut Self::State) -> i64 {
self.as_seconds()
}
#[inline]
fn unix_timestamp_milliseconds(&self, _: &mut Self::State) -> i64 {
self.as_milliseconds()
}
#[inline]
fn unix_timestamp_microseconds(&self, _: &mut Self::State) -> i128 {
self.as_microseconds()
}
#[inline]
fn unix_timestamp_nanoseconds(&self, _: &mut Self::State) -> i128 {
self.as_nanoseconds()
}
}
+961
View File
@@ -0,0 +1,961 @@
//! 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 deranged::{ru8, ru16};
use num_conv::prelude::*;
use crate::format_description::format_description_v3::FormatDescriptionV3Inner;
use crate::format_description::modifier::Padding;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::{
BorrowedFormatItem, Component, FormatDescriptionV3, OwnedFormatItem,
};
use crate::formatting::{
ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, format_four_digits_pad_zero, format_two_digits,
iso8601, write, write_bytes, write_if_else,
};
use crate::internal_macros::try_likely_ok;
use crate::{PrivateMethod, error, num_fmt};
macro_rules! fmt_component_match {
($self:expr, $output:ident, $value:ident, $state:ident, $($extra:tt)*) => {
match $self {
Self::Day(modifier) if V::SUPPLIES_DATE => {
fmt_day($output, $value.day($state), *modifier).map_err(Into::into)
}
Self::MonthShort(modifier) if V::SUPPLIES_DATE => {
fmt_month_short($output, $value.month($state), *modifier).map_err(Into::into)
}
Self::MonthLong(modifier) if V::SUPPLIES_DATE => {
fmt_month_long($output, $value.month($state), *modifier).map_err(Into::into)
}
Self::MonthNumerical(modifier) if V::SUPPLIES_DATE => {
fmt_month_numerical($output, $value.month($state), *modifier).map_err(Into::into)
}
Self::Ordinal(modifier) if V::SUPPLIES_DATE => {
fmt_ordinal($output, $value.ordinal($state), *modifier).map_err(Into::into)
}
Self::WeekdayShort(modifier) if V::SUPPLIES_DATE => {
fmt_weekday_short($output, $value.weekday($state), *modifier).map_err(Into::into)
}
Self::WeekdayLong(modifier) if V::SUPPLIES_DATE => {
fmt_weekday_long($output, $value.weekday($state), *modifier).map_err(Into::into)
}
Self::WeekdaySunday(modifier) if V::SUPPLIES_DATE => {
fmt_weekday_sunday($output, $value.weekday($state), *modifier).map_err(Into::into)
}
Self::WeekdayMonday(modifier) if V::SUPPLIES_DATE => {
fmt_weekday_monday($output, $value.weekday($state), *modifier).map_err(Into::into)
}
Self::WeekNumberIso(modifier) if V::SUPPLIES_DATE => {
fmt_week_number_iso($output, $value.iso_week_number($state), *modifier)
.map_err(Into::into)
}
Self::WeekNumberSunday(modifier) if V::SUPPLIES_DATE => {
fmt_week_number_sunday($output, $value.sunday_based_week($state), *modifier)
.map_err(Into::into)
}
Self::WeekNumberMonday(modifier) if V::SUPPLIES_DATE => {
fmt_week_number_monday($output, $value.monday_based_week($state), *modifier)
.map_err(Into::into)
}
Self::CalendarYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
fmt_calendar_year_full_extended_range(
$output,
$value.calendar_year($state),
*modifier
).map_err(Into::into)
}
Self::CalendarYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
fmt_calendar_year_full_standard_range(
$output,
try_likely_ok!(
$value
.calendar_year($state)
.narrow::<-9_999, 9_999>()
.ok_or_else(|| error::ComponentRange::conditional("year"))
)
.into(),
*modifier,
)
.map_err(Into::into)
}
Self::IsoYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
fmt_iso_year_full_extended_range($output, $value.iso_year($state), *modifier)
.map_err(Into::into)
}
Self::IsoYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
fmt_iso_year_full_standard_range(
$output,
try_likely_ok!(
$value
.iso_year($state)
.narrow::<-9_999, 9_999>()
.ok_or_else(|| error::ComponentRange::conditional("year"))
)
.into(),
*modifier,
)
.map_err(Into::into)
}
Self::CalendarYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
let year = $value.calendar_year($state);
// Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
fmt_calendar_year_century_extended_range(
$output,
century,
year.is_negative(),
*modifier,
)
.map_err(Into::into)
}
Self::CalendarYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
let year = $value.calendar_year($state);
let is_negative = year.is_negative();
// Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
let year = unsafe {
ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate())
};
fmt_calendar_year_century_standard_range(
$output,
year.narrow::<-99, 99>()
.ok_or_else(|| error::ComponentRange::conditional("year"))?
.into(),
is_negative,
*modifier,
)
.map_err(Into::into)
}
Self::IsoYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
let year = $value.iso_year($state);
let is_negative = year.is_negative();
// Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
fmt_iso_year_century_extended_range($output, century, is_negative, *modifier)
.map_err(Into::into)
}
Self::IsoYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
let year = $value.iso_year($state);
let is_negative = year.is_negative();
// Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
let year = unsafe {
ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate())
};
fmt_iso_year_century_standard_range(
$output,
year.narrow::<-99, 99>()
.ok_or_else(|| error::ComponentRange::conditional("year"))?
.into(),
is_negative,
*modifier,
)
.map_err(Into::into)
}
Self::CalendarYearLastTwo(modifier) if V::SUPPLIES_DATE => {
// Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the $value
// is in the range `0..=99`.
let last_two = unsafe {
ru8::new_unchecked(
($value.calendar_year($state).get().unsigned_abs() % 100).truncate(),
)
};
fmt_calendar_year_last_two($output, last_two, *modifier).map_err(Into::into)
}
Self::IsoYearLastTwo(modifier) if V::SUPPLIES_DATE => {
// Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the $value
// is in the range `0..=99`.
let last_two = unsafe {
ru8::new_unchecked(
($value.iso_year($state).get().unsigned_abs() % 100).truncate(),
)
};
fmt_iso_year_last_two($output, last_two, *modifier).map_err(Into::into)
}
Self::Hour12(modifier) if V::SUPPLIES_TIME => {
fmt_hour_12($output, $value.hour($state), *modifier).map_err(Into::into)
}
Self::Hour24(modifier) if V::SUPPLIES_TIME => {
fmt_hour_24($output, $value.hour($state), *modifier).map_err(Into::into)
}
Self::Minute(modifier) if V::SUPPLIES_TIME => {
fmt_minute($output, $value.minute($state), *modifier).map_err(Into::into)
}
Self::Period(modifier) if V::SUPPLIES_TIME => {
fmt_period($output, $value.period($state), *modifier).map_err(Into::into)
}
Self::Second(modifier) if V::SUPPLIES_TIME => {
fmt_second($output, $value.second($state), *modifier).map_err(Into::into)
}
Self::Subsecond(modifier) if V::SUPPLIES_TIME => {
fmt_subsecond($output, $value.nanosecond($state), *modifier).map_err(Into::into)
}
Self::OffsetHour(modifier) if V::SUPPLIES_OFFSET => fmt_offset_hour(
$output,
$value.offset_is_negative($state),
$value.offset_hour($state),
*modifier,
)
.map_err(Into::into),
Self::OffsetMinute(modifier) if V::SUPPLIES_OFFSET => {
fmt_offset_minute($output, $value.offset_minute($state), *modifier)
.map_err(Into::into)
}
Self::OffsetSecond(modifier) if V::SUPPLIES_OFFSET => {
fmt_offset_second($output, $value.offset_second($state), *modifier)
.map_err(Into::into)
}
Self::Ignore(_) => Ok(0),
Self::UnixTimestampSecond(modifier) if V::SUPPLIES_TIMESTAMP => {
fmt_unix_timestamp_second($output, $value.unix_timestamp_seconds($state), *modifier)
.map_err(Into::into)
}
Self::UnixTimestampMillisecond(modifier) if V::SUPPLIES_TIMESTAMP => {
fmt_unix_timestamp_millisecond(
$output,
$value.unix_timestamp_milliseconds($state),
*modifier,
)
.map_err(Into::into)
}
Self::UnixTimestampMicrosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
fmt_unix_timestamp_microsecond(
$output,
$value.unix_timestamp_microseconds($state),
*modifier,
)
.map_err(Into::into)
}
Self::UnixTimestampNanosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
fmt_unix_timestamp_nanosecond(
$output,
$value.unix_timestamp_nanoseconds($state),
*modifier,
)
.map_err(Into::into)
}
Self::End(modifier::End { trailing_input: _ }) => Ok(0),
$($extra)*
}
};
}
/// A type that describes a format.
///
/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
///
/// To format a value into a String, use the `format` method on the respective type.
#[cfg_attr(docsrs, doc(notable_trait))]
pub trait Formattable: sealed::Sealed {}
impl Formattable for FormatDescriptionV3<'_> {}
impl Formattable for BorrowedFormatItem<'_> {}
impl Formattable for [BorrowedFormatItem<'_>] {}
impl Formattable for OwnedFormatItem {}
impl Formattable for [OwnedFormatItem] {}
impl Formattable for Rfc3339 {}
impl Formattable for Rfc2822 {}
impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
impl<T> Formattable for T where T: Deref<Target: Formattable> {}
/// Seal the trait to prevent downstream users from implementing it.
mod sealed {
use super::*;
use crate::formatting::ComponentProvider;
use crate::formatting::metadata::ComputeMetadata;
/// Format the item using a format description, the intended output, and the various components.
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
pub trait Sealed: ComputeMetadata {
/// Format the item into the provided output, returning the number of bytes written.
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider;
/// Format the item directly to a `String`.
#[inline]
fn format<V>(
&self,
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<String, error::Format>
where
V: ComponentProvider,
{
let crate::formatting::metadata::Metadata {
max_bytes_needed,
guaranteed_utf8,
} = self.compute_metadata(PrivateMethod);
let mut buf = Vec::with_capacity(max_bytes_needed);
try_likely_ok!(self.format_into(&mut buf, value, state, PrivateMethod));
Ok(if guaranteed_utf8 {
// Safety: The output is guaranteed to be UTF-8.
unsafe { String::from_utf8_unchecked(buf) }
} else {
String::from_utf8_lossy(&buf).into_owned()
})
}
}
}
impl sealed::Sealed for FormatDescriptionV3<'_> {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
self.inner.format_into(output, value, state, PrivateMethod)
}
}
impl sealed::Sealed for FormatDescriptionV3Inner<'_> {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
use crate::formatting::*;
fmt_component_match! { &self, output, value, state,
Self::BorrowedLiteral(literal) => {
write_bytes(output, literal.as_bytes()).map_err(Into::into)
}
Self::BorrowedCompound(items) => {
let mut bytes = 0;
for item in *items {
bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
}
Ok(bytes)
}
Self::BorrowedOptional {
format: should_format,
item,
} => {
if *should_format {
item.format_into(output, value, state, PrivateMethod)
} else {
Ok(0)
}
}
Self::BorrowedFirst(items) => match items {
[] => Ok(0),
[item, ..] => item.format_into(output, value, state, PrivateMethod),
},
Self::OwnedLiteral(literal) => {
write_bytes(output, literal.as_bytes()).map_err(Into::into)
}
Self::OwnedCompound(items) => {
let mut bytes = 0;
for item in &**items {
bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
}
Ok(bytes)
}
Self::OwnedOptional {
format: should_format,
item,
} => {
if *should_format {
item.format_into(output, value, state, PrivateMethod)
} else {
Ok(0)
}
}
Self::OwnedFirst(items) => match &items[..] {
[] => Ok(0),
[item, ..] => item.format_into(output, value, state, PrivateMethod),
},
// 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)]
Self::Day(_)
| Self::MonthShort(_)
| Self::MonthLong(_)
| Self::MonthNumerical(_)
| Self::Ordinal(_)
| Self::WeekdayShort(_)
| Self::WeekdayLong(_)
| Self::WeekdaySunday(_)
| Self::WeekdayMonday(_)
| Self::WeekNumberIso(_)
| Self::WeekNumberSunday(_)
| Self::WeekNumberMonday(_)
| Self::CalendarYearFullExtendedRange(_)
| Self::CalendarYearFullStandardRange(_)
| Self::IsoYearFullExtendedRange(_)
| Self::IsoYearFullStandardRange(_)
| Self::CalendarYearCenturyExtendedRange(_)
| Self::CalendarYearCenturyStandardRange(_)
| Self::IsoYearCenturyExtendedRange(_)
| Self::IsoYearCenturyStandardRange(_)
| Self::CalendarYearLastTwo(_)
| Self::IsoYearLastTwo(_)
| Self::Hour12(_)
| Self::Hour24(_)
| Self::Minute(_)
| Self::Period(_)
| Self::Second(_)
| Self::Subsecond(_)
| Self::OffsetHour(_)
| Self::OffsetMinute(_)
| Self::OffsetSecond(_)
| Self::Ignore(_)
| Self::UnixTimestampSecond(_)
| Self::UnixTimestampMillisecond(_)
| Self::UnixTimestampMicrosecond(_)
| Self::UnixTimestampNanosecond(_)
| Self::End(_) => Err(error::Format::InsufficientTypeInformation),
}
}
}
impl Component {
/// Format the component directly into the provided output.
#[inline]
#[allow(deprecated)]
pub(crate) fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
use sealed::Sealed;
use crate::formatting::*;
fmt_component_match! { self, output, value, state,
// Deprecated component variants: delegate through the existing conversion.
Self::Month(_) | Self::Weekday(_) | Self::WeekNumber(_)
if V::SUPPLIES_DATE =>
{
FormatDescriptionV3Inner::from(*self)
.format_into(output, value, state, PrivateMethod)
}
Self::Hour(_) if V::SUPPLIES_TIME => {
FormatDescriptionV3Inner::from(*self)
.format_into(output, value, state, PrivateMethod)
}
Self::UnixTimestamp(_) if V::SUPPLIES_TIMESTAMP => {
FormatDescriptionV3Inner::from(*self)
.format_into(output, value, state, PrivateMethod)
}
Self::Year(_) if V::SUPPLIES_DATE => {
FormatDescriptionV3Inner::from(*self)
.format_into(output, value, state, PrivateMethod)
}
// Unmatched component (e.g. time component on a date-only type).
#[allow(unreachable_patterns)]
Self::Day(_)
| Self::MonthShort(_)
| Self::MonthLong(_)
| Self::MonthNumerical(_)
| Self::Ordinal(_)
| Self::WeekdayShort(_)
| Self::WeekdayLong(_)
| Self::WeekdaySunday(_)
| Self::WeekdayMonday(_)
| Self::WeekNumberIso(_)
| Self::WeekNumberSunday(_)
| Self::WeekNumberMonday(_)
| Self::CalendarYearFullExtendedRange(_)
| Self::CalendarYearFullStandardRange(_)
| Self::IsoYearFullExtendedRange(_)
| Self::IsoYearFullStandardRange(_)
| Self::CalendarYearCenturyExtendedRange(_)
| Self::CalendarYearCenturyStandardRange(_)
| Self::IsoYearCenturyExtendedRange(_)
| Self::IsoYearCenturyStandardRange(_)
| Self::CalendarYearLastTwo(_)
| Self::IsoYearLastTwo(_)
| Self::Hour12(_)
| Self::Hour24(_)
| Self::Minute(_)
| Self::Period(_)
| Self::Second(_)
| Self::Subsecond(_)
| Self::OffsetHour(_)
| Self::OffsetMinute(_)
| Self::OffsetSecond(_)
| Self::Ignore(_)
| Self::UnixTimestampSecond(_)
| Self::UnixTimestampMillisecond(_)
| Self::UnixTimestampMicrosecond(_)
| Self::UnixTimestampNanosecond(_)
// Deprecated variants not matched by guarded arms above.
| Self::Month(_)
| Self::Weekday(_)
| Self::WeekNumber(_)
| Self::Hour(_)
| Self::UnixTimestamp(_)
| Self::Year(_) => Err(error::Format::InsufficientTypeInformation),
}
}
}
impl sealed::Sealed for BorrowedFormatItem<'_> {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
Ok(match *self {
#[expect(deprecated)]
Self::Literal(literal) => try_likely_ok!(write_bytes(output, literal)),
Self::StringLiteral(literal) => try_likely_ok!(write(output, literal)),
Self::Component(component) => component.format_into(output, value, state)?,
Self::Compound(items) => {
try_likely_ok!((*items).format_into(output, value, state, PrivateMethod))
}
Self::Optional(item) => {
try_likely_ok!((*item).format_into(output, value, state, PrivateMethod))
}
Self::First(items) => match items {
[] => 0,
[item, ..] => {
try_likely_ok!((*item).format_into(output, value, state, PrivateMethod))
}
},
})
}
}
impl sealed::Sealed for [BorrowedFormatItem<'_>] {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
let mut bytes = 0;
for item in self.iter() {
bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
}
Ok(bytes)
}
}
impl sealed::Sealed for OwnedFormatItem {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
match self {
#[expect(deprecated)]
Self::Literal(literal) => Ok(try_likely_ok!(write_bytes(output, literal))),
Self::StringLiteral(literal) => Ok(try_likely_ok!(write(output, literal))),
Self::Component(component) => FormatDescriptionV3Inner::<'_>::from(*component)
.format_into(output, value, state, PrivateMethod),
Self::Compound(items) => (**items).format_into(output, value, state, PrivateMethod),
Self::Optional(item) => (**item).format_into(output, value, state, PrivateMethod),
Self::First(items) => match &**items {
[] => Ok(0),
[item, ..] => (*item).format_into(output, value, state, PrivateMethod),
},
}
}
}
impl sealed::Sealed for [OwnedFormatItem] {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
let mut bytes = 0;
for item in self.iter() {
bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
}
Ok(bytes)
}
}
impl<T> sealed::Sealed for T
where
T: Deref<Target: sealed::Sealed>,
{
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
self.deref()
.format_into(output, value, state, PrivateMethod)
}
}
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
impl sealed::Sealed for Rfc2822 {
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
const {
assert!(
V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
"Rfc2822 requires date, time, and offset components, but not all can be provided \
by this type"
);
}
let mut bytes = 0;
if value.calendar_year(state).get() < 1900
// The RFC requires years be exactly four digits.
|| (cfg!(feature = "large-dates") && value.calendar_year(state).get() >= 10_000)
{
crate::hint::cold_path();
return Err(error::Format::InvalidComponent("year"));
}
if value.offset_second(state).get() != 0 {
crate::hint::cold_path();
return Err(error::Format::InvalidComponent("offset_second"));
}
// Safety: All weekday names are at least 3 bytes long.
bytes += try_likely_ok!(write(output, unsafe {
WEEKDAY_NAMES[value
.weekday(state)
.number_days_from_monday()
.widen::<usize>()]
.get_unchecked(..3)
}));
bytes += try_likely_ok!(write(output, ", "));
bytes += try_likely_ok!(format_two_digits(
output,
value.day(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, " "));
// Safety: All month names are at least 3 bytes long.
bytes += try_likely_ok!(write(output, unsafe {
MONTH_NAMES[u8::from(value.month(state)).widen::<usize>() - 1].get_unchecked(..3)
}));
bytes += try_likely_ok!(write(output, " "));
// Safety: Years with five or more digits were rejected above. Likewise with negative years.
bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
}));
bytes += try_likely_ok!(write(output, " "));
bytes += try_likely_ok!(format_two_digits(
output,
value.hour(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, ":"));
bytes += try_likely_ok!(format_two_digits(
output,
value.minute(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, ":"));
bytes += try_likely_ok!(format_two_digits(
output,
value.second(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, " "));
bytes += try_likely_ok!(write_if_else(
output,
value.offset_is_negative(state),
"-",
"+"
));
bytes += try_likely_ok!(format_two_digits(
output,
// Safety: `OffsetHours` is guaranteed to be in the range `-25..=25`, so the absolute
// value is guaranteed to be in the range `0..=25`.
unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
Padding::Zero,
));
bytes += try_likely_ok!(format_two_digits(
output,
// Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
// value is guaranteed to be in the range `0..=59`.
unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
Padding::Zero,
));
Ok(bytes)
}
}
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
impl sealed::Sealed for Rfc3339 {
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
const {
assert!(
V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
"Rfc3339 requires date, time, and offset components, but not all can be provided \
by this type"
);
}
let offset_hour = value.offset_hour(state);
let mut bytes = 0;
if !(0..10_000).contains(&value.calendar_year(state).get()) {
crate::hint::cold_path();
return Err(error::Format::InvalidComponent("year"));
}
if offset_hour.get().unsigned_abs() > 23 {
crate::hint::cold_path();
return Err(error::Format::InvalidComponent("offset_hour"));
}
if value.offset_second(state).get() != 0 {
crate::hint::cold_path();
return Err(error::Format::InvalidComponent("offset_second"));
}
// Safety: Years outside this range were rejected above.
bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
}));
bytes += try_likely_ok!(write(output, "-"));
bytes += try_likely_ok!(format_two_digits(
output,
// Safety: `month` is guaranteed to be in the range `1..=12`.
unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
Padding::Zero,
));
bytes += try_likely_ok!(write(output, "-"));
bytes += try_likely_ok!(format_two_digits(
output,
value.day(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, "T"));
bytes += try_likely_ok!(format_two_digits(
output,
value.hour(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, ":"));
bytes += try_likely_ok!(format_two_digits(
output,
value.minute(state).expand(),
Padding::Zero
));
bytes += try_likely_ok!(write(output, ":"));
bytes += try_likely_ok!(format_two_digits(
output,
value.second(state).expand(),
Padding::Zero
));
let nanos = value.nanosecond(state);
if nanos.get() != 0 {
bytes += try_likely_ok!(write(output, "."));
try_likely_ok!(write(
output,
&num_fmt::truncated_subsecond_from_nanos(nanos)
));
}
if value.offset_is_utc(state) {
bytes += try_likely_ok!(write(output, "Z"));
return Ok(bytes);
}
bytes += try_likely_ok!(write_if_else(
output,
value.offset_is_negative(state),
"-",
"+"
));
bytes += try_likely_ok!(format_two_digits(
output,
// Safety: `OffsetHours` is guaranteed to be in the range `-23..=23`, so the absolute
// value is guaranteed to be in the range `0..=23`.
unsafe { ru8::new_unchecked(offset_hour.get().unsigned_abs()) },
Padding::Zero,
));
bytes += try_likely_ok!(write(output, ":"));
bytes += try_likely_ok!(format_two_digits(
output,
// Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
// value is guaranteed to be in the range `0..=59`.
unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
Padding::Zero,
));
Ok(bytes)
}
}
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
#[expect(
private_bounds,
private_interfaces,
reason = "irrelevant due to being a sealed trait"
)]
#[inline]
fn format_into<V>(
&self,
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
_: PrivateMethod,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
let mut bytes = 0;
const {
assert!(
!Self::FORMAT_DATE || V::SUPPLIES_DATE,
"this Iso8601 configuration formats date components, but this type cannot provide \
them"
);
assert!(
!Self::FORMAT_TIME || V::SUPPLIES_TIME,
"this Iso8601 configuration formats time components, but this type cannot provide \
them"
);
assert!(
!Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
"this Iso8601 configuration formats offset components, but this type cannot \
provide them"
);
assert!(
Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
"this Iso8601 configuration does not format any components"
);
}
if Self::FORMAT_DATE {
bytes += try_likely_ok!(iso8601::format_date::<_, CONFIG>(output, value, state));
}
if Self::FORMAT_TIME {
bytes += try_likely_ok!(iso8601::format_time::<_, CONFIG>(output, value, state));
}
if Self::FORMAT_OFFSET {
bytes += try_likely_ok!(iso8601::format_offset::<_, CONFIG>(output, value, state));
}
Ok(bytes)
}
}
+217
View File
@@ -0,0 +1,217 @@
//! Helpers for implementing formatting for ISO 8601.
use core::cmp::min;
use std::io;
use deranged::{ru8, ru16, ru32};
use num_conv::prelude::*;
use crate::error;
use crate::format_description::modifier::Padding;
use crate::format_description::well_known::Iso8601;
use crate::format_description::well_known::iso8601::{
DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
};
use crate::formatting::{
ComponentProvider, format_float, format_four_digits_pad_zero, format_int_padded,
format_single_digit, format_six_digits_pad_zero, format_three_digits, format_two_digits, write,
write_if, write_if_else,
};
use crate::unit::*;
/// Format the date portion of ISO 8601.
pub(super) fn format_date<V, const CONFIG: EncodedConfig>(
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
let mut bytes = 0;
match Iso8601::<CONFIG>::DATE_KIND {
DateKind::Calendar => {
let year = value.calendar_year(state).get();
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
bytes += write_if_else(output, year < 0, "-", "+")?;
// Safety: `calendar_year` returns a value whose absolute value is guaranteed to be
// less than 1,000,000.
bytes += format_six_digits_pad_zero(output, unsafe {
ru32::new_unchecked(year.unsigned_abs())
})?;
} else {
let year = ru16::new(year.cast_unsigned().truncate())
.ok_or(error::Format::InvalidComponent("year"))?;
bytes += format_four_digits_pad_zero(output, year)?;
}
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, "-")?;
// Safety: `month` is guaranteed to be in the range `1..=12`.
bytes += format_two_digits(
output,
unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
Padding::Zero,
)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, "-")?;
bytes += format_two_digits(output, value.day(state).expand(), Padding::Zero)?;
}
DateKind::Week => {
let year = value.iso_year(state).get();
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
bytes += write_if_else(output, year < 0, "-", "+")?;
// Safety: `iso_year` returns a value whose absolute value is guaranteed to be less
// than 1,000,000.
bytes += format_six_digits_pad_zero(output, unsafe {
ru32::new_unchecked(year.unsigned_abs())
})?;
} else {
let year = ru16::new(year.cast_unsigned().truncate())
.ok_or(error::Format::InvalidComponent("year"))?;
bytes += format_four_digits_pad_zero(output, year)?;
}
bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, "-W", "W")?;
bytes +=
format_two_digits(output, value.iso_week_number(state).expand(), Padding::Zero)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, "-")?;
// Safety: The value is in the range `1..=7`.
bytes += format_single_digit(output, unsafe {
ru8::new_unchecked(value.weekday(state).number_from_monday())
})?;
}
DateKind::Ordinal => {
let year = value.calendar_year(state).get();
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
bytes += write_if_else(output, year < 0, "-", "+")?;
// Safety: `calendar_year` returns a value whose absolute value is guaranteed to be
// less than 1,000,000.
bytes += format_six_digits_pad_zero(output, unsafe {
ru32::new_unchecked(year.unsigned_abs())
})?;
} else {
let year = ru16::new(year.cast_unsigned().truncate())
.ok_or(error::Format::InvalidComponent("year"))?;
bytes += format_four_digits_pad_zero(output, year)?;
}
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, "-")?;
bytes += format_three_digits(output, value.ordinal(state).expand(), Padding::Zero)?;
}
}
Ok(bytes)
}
/// Format the time portion of ISO 8601.
#[inline]
pub(super) fn format_time<V, const CONFIG: EncodedConfig>(
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
let mut bytes = 0;
// The "T" can only be omitted in extended format where there is no date being formatted.
bytes += write_if(
output,
!Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE,
"T",
)?;
match Iso8601::<CONFIG>::TIME_PRECISION {
TimePrecision::Hour { decimal_digits } => {
let hours = (value.hour(state).get() as f64)
+ (value.minute(state).get() as f64) / Minute::per_t::<f64>(Hour)
+ (value.second(state).get() as f64) / Second::per_t::<f64>(Hour)
+ (value.nanosecond(state).get() as f64) / Nanosecond::per_t::<f64>(Hour);
format_float(output, hours, 2, decimal_digits)?;
}
TimePrecision::Minute { decimal_digits } => {
bytes += format_two_digits(output, value.hour(state).expand(), Padding::Zero)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, ":")?;
let minutes = (value.minute(state).get() as f64)
+ (value.second(state).get() as f64) / Second::per_t::<f64>(Minute)
+ (value.nanosecond(state).get() as f64) / Nanosecond::per_t::<f64>(Minute);
bytes += format_float(output, minutes, 2, decimal_digits)?;
}
TimePrecision::Second { decimal_digits } => {
bytes += format_two_digits(output, value.hour(state).expand(), Padding::Zero)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, ":")?;
bytes += format_two_digits(output, value.minute(state).expand(), Padding::Zero)?;
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, ":")?;
bytes += format_two_digits(output, value.second(state).expand(), Padding::Zero)?;
if let Some(digits) = decimal_digits {
const POW_TABLE: [u64; 9] = [
1,
10,
100,
1_000,
10_000,
100_000,
1_000_000,
10_000_000,
100_000_000,
];
bytes += write(output, ".")?;
let nano = value.nanosecond(state).get() as u64;
let sub_digits = min(digits.get(), 9);
let truncated = nano / POW_TABLE[9 - sub_digits as usize];
bytes += format_int_padded(output, truncated, sub_digits)?;
for _ in 9..digits.get() {
bytes += write(output, "0")?;
}
}
}
}
Ok(bytes)
}
/// Format the UTC offset portion of ISO 8601.
#[inline]
pub(super) fn format_offset<V, const CONFIG: EncodedConfig>(
output: &mut (impl io::Write + ?Sized),
value: &V,
state: &mut V::State,
) -> Result<usize, error::Format>
where
V: ComponentProvider,
{
if Iso8601::<CONFIG>::FORMAT_TIME && value.offset_is_utc(state) {
return Ok(write(output, "Z")?);
}
let mut bytes = 0;
if value.offset_second(state).get() != 0 {
return Err(error::Format::InvalidComponent("offset_second"));
}
bytes += write_if_else(output, value.offset_is_negative(state), "-", "+")?;
// Safety: The value is in the range `-25..=25`.
bytes += format_two_digits(
output,
unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
Padding::Zero,
)?;
let minutes = value.offset_minute(state);
if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes.get() != 0 {
return Err(error::Format::InvalidComponent("offset_minute"));
} else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute {
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, ":")?;
// Safety: The value is in the range `0..=59`.
bytes += format_two_digits(
output,
unsafe { ru8::new_unchecked(minutes.get().unsigned_abs()) },
Padding::Zero,
)?;
}
Ok(bytes)
}
+375
View File
@@ -0,0 +1,375 @@
use core::iter::Sum;
use core::ops::{Add, Deref};
use crate::PrivateMethod;
use crate::format_description::format_description_v3::FormatDescriptionV3Inner;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::{
BorrowedFormatItem, Component, FormatDescriptionV3, OwnedFormatItem, modifier,
};
use crate::internal_macros::bug;
/// Metadata about a format description.
#[derive(Debug)]
pub(crate) struct Metadata {
/// The maximum number of bytes needed for the provided format description.
///
/// The number of bytes written should never exceed this value, but it may be less. This is
/// used to pre-allocate a buffer of the appropriate size for formatting.
pub(crate) max_bytes_needed: usize,
/// Whether the output of the provided format description is guaranteed to be valid UTF-8.
///
/// This is used to determine whether the output can be soundly converted to a `String` without
/// checking for UTF-8 validity.
pub(crate) guaranteed_utf8: bool,
}
impl Default for Metadata {
#[inline]
fn default() -> Self {
Self {
max_bytes_needed: 0,
guaranteed_utf8: true,
}
}
}
impl Add for Metadata {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Self {
max_bytes_needed: self.max_bytes_needed + rhs.max_bytes_needed,
guaranteed_utf8: self.guaranteed_utf8 && rhs.guaranteed_utf8,
}
}
}
impl Sum for Metadata {
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
iter.fold(Self::default(), Self::add)
}
}
/// A trait for computing metadata about a format description.
pub(crate) trait ComputeMetadata {
/// Compute the metadata for a format description.
fn compute_metadata(&self, _: PrivateMethod) -> Metadata;
}
impl ComputeMetadata for Rfc2822 {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
Metadata {
max_bytes_needed: 31,
guaranteed_utf8: true,
}
}
}
impl ComputeMetadata for Rfc3339 {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
Metadata {
max_bytes_needed: 35,
guaranteed_utf8: true,
}
}
}
impl<const CONFIG: EncodedConfig> ComputeMetadata for Iso8601<CONFIG> {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
const {
use crate::format_description::well_known::iso8601::{
DateKind, OffsetPrecision, TimePrecision,
};
let date_width = if Self::FORMAT_DATE {
let year_width = if Self::YEAR_IS_SIX_DIGITS {
7 // sign + 6 digits
} else {
4 // sign is not present when the year is four digits
};
let num_dashes = match Self::DATE_KIND {
DateKind::Calendar if Self::USE_SEPARATORS => 2,
DateKind::Week | DateKind::Ordinal if Self::USE_SEPARATORS => 1,
DateKind::Calendar | DateKind::Week | DateKind::Ordinal => 0,
};
let part_of_year_width = match Self::DATE_KIND {
DateKind::Calendar => 4,
DateKind::Week => 4,
DateKind::Ordinal => 3,
};
year_width + num_dashes + part_of_year_width
} else {
0
};
let time_width = if Self::FORMAT_TIME {
let t_separator = (Self::USE_SEPARATORS || Self::FORMAT_DATE) as usize;
let num_colons = match Self::TIME_PRECISION {
TimePrecision::Minute { .. } if Self::USE_SEPARATORS => 1,
TimePrecision::Second { .. } if Self::USE_SEPARATORS => 2,
TimePrecision::Hour { .. }
| TimePrecision::Minute { .. }
| TimePrecision::Second { .. } => 0,
};
let pre_decimal_digits = match Self::TIME_PRECISION {
TimePrecision::Hour { .. } => 2,
TimePrecision::Minute { .. } => 4,
TimePrecision::Second { .. } => 6,
};
let fractional_bytes = match Self::TIME_PRECISION {
TimePrecision::Hour { decimal_digits }
| TimePrecision::Minute { decimal_digits }
| TimePrecision::Second { decimal_digits } => {
if let Some(digits) = decimal_digits {
// add one for decimal point
1 + digits.get() as usize
} else {
0
}
}
};
t_separator + num_colons + pre_decimal_digits + fractional_bytes
} else {
0
};
let offset_width = if Self::FORMAT_OFFSET {
match Self::OFFSET_PRECISION {
OffsetPrecision::Hour => 3,
OffsetPrecision::Minute if Self::USE_SEPARATORS => 6,
OffsetPrecision::Minute => 5,
}
} else {
0
};
Metadata {
max_bytes_needed: date_width + time_width + offset_width,
guaranteed_utf8: true,
}
}
}
}
impl ComputeMetadata for FormatDescriptionV3<'_> {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
Metadata {
max_bytes_needed: self.max_bytes_needed,
guaranteed_utf8: true,
}
}
}
impl ComputeMetadata for FormatDescriptionV3Inner<'_> {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
bug!(
"`FormatDescriptionV3Inner` should never be directly used to compute metadata. \
Instead, the metadata should be pre-computed and stored in `FormatDescriptionV3`."
)
}
}
impl ComputeMetadata for BorrowedFormatItem<'_> {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
match self {
#[expect(deprecated)]
Self::Literal(bytes) => Metadata {
max_bytes_needed: bytes.len(),
guaranteed_utf8: false,
},
Self::StringLiteral(s) => Metadata {
max_bytes_needed: s.len(),
guaranteed_utf8: true,
},
Self::Component(component) => component.compute_metadata(PrivateMethod),
Self::Compound(borrowed_format_items) => {
borrowed_format_items.compute_metadata(PrivateMethod)
}
Self::Optional(borrowed_format_item) => {
borrowed_format_item.compute_metadata(PrivateMethod)
}
Self::First(borrowed_format_items) => borrowed_format_items
.first()
.map_or_else(Metadata::default, |item| {
item.compute_metadata(PrivateMethod)
}),
}
}
}
impl ComputeMetadata for OwnedFormatItem {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
match self {
#[expect(deprecated)]
Self::Literal(bytes) => Metadata {
max_bytes_needed: bytes.len(),
guaranteed_utf8: false,
},
Self::StringLiteral(s) => Metadata {
max_bytes_needed: s.len(),
guaranteed_utf8: true,
},
Self::Component(component) => component.compute_metadata(PrivateMethod),
Self::Compound(owned_format_items) => {
owned_format_items.compute_metadata(PrivateMethod)
}
Self::Optional(owned_format_item) => owned_format_item.compute_metadata(PrivateMethod),
Self::First(owned_format_items) => owned_format_items
.first()
.map_or_else(Metadata::default, |item| {
item.compute_metadata(PrivateMethod)
}),
}
}
}
impl ComputeMetadata for Component {
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
let max_bytes_needed = match self {
Self::Day(_) => 2,
Self::MonthShort(_) => 3,
Self::MonthLong(_) => 9,
Self::MonthNumerical(_) => 2,
Self::Ordinal(_) => 3,
Self::WeekdayShort(_) => 3,
Self::WeekdayLong(_) => 9,
Self::WeekdaySunday(_) | Self::WeekdayMonday(_) => 1,
Self::WeekNumberIso(_) | Self::WeekNumberSunday(_) | Self::WeekNumberMonday(_) => 2,
Self::CalendarYearFullExtendedRange(_) => 7,
Self::CalendarYearFullStandardRange(_) => 5,
Self::IsoYearFullExtendedRange(_) => 7,
Self::IsoYearFullStandardRange(_) => 5,
Self::CalendarYearCenturyExtendedRange(_) => 5,
Self::CalendarYearCenturyStandardRange(_) => 3,
Self::IsoYearCenturyExtendedRange(_) => 5,
Self::IsoYearCenturyStandardRange(_) => 3,
Self::CalendarYearLastTwo(_) => 2,
Self::IsoYearLastTwo(_) => 2,
Self::Hour12(_) | Self::Hour24(_) => 2,
Self::Minute(_) | Self::Period(_) | Self::Second(_) => 2,
Self::Subsecond(modifier) => match modifier.digits {
modifier::SubsecondDigits::One => 1,
modifier::SubsecondDigits::Two => 2,
modifier::SubsecondDigits::Three => 3,
modifier::SubsecondDigits::Four => 4,
modifier::SubsecondDigits::Five => 5,
modifier::SubsecondDigits::Six => 6,
modifier::SubsecondDigits::Seven => 7,
modifier::SubsecondDigits::Eight => 8,
modifier::SubsecondDigits::Nine => 9,
modifier::SubsecondDigits::OneOrMore => 9,
},
Self::OffsetHour(_) => 3,
Self::OffsetMinute(_) | Self::OffsetSecond(_) => 2,
#[cfg(feature = "large-dates")]
Self::UnixTimestampSecond(_) => 15,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampSecond(_) => 13,
#[cfg(feature = "large-dates")]
Self::UnixTimestampMillisecond(_) => 18,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampMillisecond(_) => 16,
#[cfg(feature = "large-dates")]
Self::UnixTimestampMicrosecond(_) => 21,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampMicrosecond(_) => 19,
#[cfg(feature = "large-dates")]
Self::UnixTimestampNanosecond(_) => 24,
#[cfg(not(feature = "large-dates"))]
Self::UnixTimestampNanosecond(_) => 22,
Self::Ignore(_) | Self::End(_) => 0,
// Start of deprecated components that are no longer emitted by macros or parsers.
#[expect(deprecated)]
Self::Month(modifier) => match modifier.repr {
modifier::MonthRepr::Numerical => 2,
modifier::MonthRepr::Long => 9,
modifier::MonthRepr::Short => 3,
},
#[expect(deprecated)]
Self::Weekday(modifier) => match modifier.repr {
modifier::WeekdayRepr::Short => 3,
modifier::WeekdayRepr::Long => 9,
modifier::WeekdayRepr::Sunday | modifier::WeekdayRepr::Monday => 1,
},
#[expect(deprecated)]
Self::WeekNumber(_) => 2,
#[expect(deprecated)]
Self::Hour(_) => 2,
#[cfg(feature = "large-dates")]
#[expect(deprecated)]
Self::UnixTimestamp(modifier) => match modifier.precision {
modifier::UnixTimestampPrecision::Second => 15,
modifier::UnixTimestampPrecision::Millisecond => 18,
modifier::UnixTimestampPrecision::Microsecond => 21,
modifier::UnixTimestampPrecision::Nanosecond => 24,
},
#[cfg(not(feature = "large-dates"))]
#[expect(deprecated)]
Self::UnixTimestamp(modifier) => match modifier.precision {
modifier::UnixTimestampPrecision::Second => 13,
modifier::UnixTimestampPrecision::Millisecond => 16,
modifier::UnixTimestampPrecision::Microsecond => 19,
modifier::UnixTimestampPrecision::Nanosecond => 22,
},
#[cfg(feature = "large-dates")]
#[expect(deprecated)]
Self::Year(modifier) => match modifier.repr {
modifier::YearRepr::Full => 7,
modifier::YearRepr::Century => 5,
modifier::YearRepr::LastTwo => 2,
},
#[cfg(not(feature = "large-dates"))]
#[expect(deprecated)]
Self::Year(modifier) => match modifier.repr {
modifier::YearRepr::Full => 5,
modifier::YearRepr::Century => 3,
modifier::YearRepr::LastTwo => 2,
},
};
Metadata {
max_bytes_needed,
guaranteed_utf8: true,
}
}
}
impl<T> ComputeMetadata for [T]
where
T: ComputeMetadata,
{
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
self.iter()
.map(|item| item.compute_metadata(PrivateMethod))
.sum()
}
}
impl<T> ComputeMetadata for T
where
T: Deref<Target: ComputeMetadata>,
{
#[inline]
fn compute_metadata(&self, _: PrivateMethod) -> Metadata {
self.deref().compute_metadata(PrivateMethod)
}
}
+942
View File
@@ -0,0 +1,942 @@
//! Formatting for various types.
mod component_provider;
pub(crate) mod formattable;
mod iso8601;
mod metadata;
use core::mem::MaybeUninit;
use core::num::NonZero;
use std::io;
use deranged::{Option_ri32, Option_ru8, ri8, ri16, ri32, ru8, ru16, ru32};
use num_conv::prelude::*;
use self::component_provider::ComponentProvider;
pub use self::formattable::Formattable;
use crate::format_description::{Period, modifier};
use crate::internal_macros::try_likely_ok;
use crate::time::{Hours, Minutes, Nanoseconds, Seconds};
use crate::utc_offset::{Hours as OffsetHours, Minutes as OffsetMinutes, Seconds as OffsetSeconds};
use crate::{Month, Weekday, error, num_fmt};
type Day = ru8<1, 31>;
type OptionDay = Option_ru8<1, 31>;
type Ordinal = ru16<1, 366>;
type IsoWeekNumber = ru8<1, 53>;
type OptionIsoWeekNumber = Option_ru8<1, 53>;
type MondayBasedWeek = ru8<0, 53>;
type SundayBasedWeek = ru8<0, 53>;
type Year = ri32<-999_999, 999_999>;
type StandardYear = ri16<-9_999, 9_999>;
type OptionYear = Option_ri32<-999_999, 999_999>;
type ExtendedCentury = ri16<-9_999, 9_999>;
type StandardCentury = ri8<-99, 99>;
type LastTwo = ru8<0, 99>;
const MONTH_NAMES: [&str; 12] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const WEEKDAY_NAMES: [&str; 7] = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];
/// Write all bytes to the output, returning the number of bytes written.
#[inline]
pub(crate) fn write_bytes(
output: &mut (impl io::Write + ?Sized),
bytes: &[u8],
) -> io::Result<usize> {
try_likely_ok!(output.write_all(bytes));
Ok(bytes.len())
}
/// Write the string to the output, returning the number of bytes written.
#[inline]
pub(crate) fn write(output: &mut (impl io::Write + ?Sized), s: &str) -> io::Result<usize> {
try_likely_ok!(output.write_all(s.as_bytes()));
Ok(s.len())
}
/// Write all strings to the output (in order), returning the total number of bytes written.
#[inline]
pub(crate) fn write_many<const N: usize>(
output: &mut (impl io::Write + ?Sized),
arr: [&str; N],
) -> io::Result<usize> {
let mut bytes = 0;
for s in arr {
try_likely_ok!(output.write_all(s.as_bytes()));
bytes += s.len();
}
Ok(bytes)
}
/// If `pred` is true, write the string to the output, returning the number of bytes written.
#[inline]
pub(crate) fn write_if(
output: &mut (impl io::Write + ?Sized),
pred: bool,
s: &str,
) -> io::Result<usize> {
if pred { write(output, s) } else { Ok(0) }
}
/// If `pred` is true, write `true_str` to the output. Otherwise, write `false_str`.
#[inline]
pub(crate) fn write_if_else(
output: &mut (impl io::Write + ?Sized),
pred: bool,
true_str: &str,
false_str: &str,
) -> io::Result<usize> {
write(output, if pred { true_str } else { false_str })
}
/// Helper function to obtain 10^x, guaranteeing determinism for x ≤ 9. For these cases, the
/// function optimizes to a lookup table. For x ≥ 10, it falls back to `10_f64.powi(x)`. The only
/// situation where this would occur is if the user explicitly requests such precision when
/// configuring the ISO 8601 well known format. All other possibilities max out at nine digits.
#[inline]
fn f64_10_pow_x(x: NonZero<u8>) -> f64 {
match x.get() {
1 => 10.,
2 => 100.,
3 => 1_000.,
4 => 10_000.,
5 => 100_000.,
6 => 1_000_000.,
7 => 10_000_000.,
8 => 100_000_000.,
9 => 1_000_000_000.,
x => 10_f64.powi(x.cast_signed().widen()),
}
}
/// Write an integer with zeros as trailing padding if necessary to reach the requested width.
///
/// This function is intended to be used for formatting the fractional part of a value, as the
/// trailing zeros would change the semantic meaning for non-fractional values.
#[inline]
fn format_int_padded(
output: &mut (impl io::Write + ?Sized),
value: u64,
width: u8,
) -> io::Result<usize> {
let s = num_fmt::u64_pad_none(value);
let digit_count = s.len() as u8;
for _ in digit_count..width {
try_likely_ok!(output.write_all(b"0"));
}
try_likely_ok!(output.write_all(s.as_bytes()));
Ok(width as usize)
}
/// Write the floating point number to the output, returning the number of bytes written.
///
/// This method accepts the number of digits before and after the decimal. The value will be padded
/// with zeroes to the left if necessary.
#[inline]
pub(crate) fn format_float(
output: &mut (impl io::Write + ?Sized),
mut value: f64,
digits_before_decimal: u8,
digits_after_decimal: Option<NonZero<u8>>,
) -> io::Result<usize> {
match digits_after_decimal {
Some(digits_after_decimal) => {
// If the precision is less than nine digits after the decimal point, truncate the
// value. This avoids rounding up and causing the value to exceed the maximum permitted
// value (as in #678). If the precision is at least nine, then we don't truncate so as
// to avoid having an off-by-one error (as in #724). The latter is necessary
// because floating point values are inherently imprecise with decimal
// values, so a minuscule error can be amplified easily.
//
// Note that this is largely an issue for second values, as for minute and hour decimals
// the value is divided by 60 or 3,600, neither of which divide evenly into 10^x.
//
// While not a perfect approach, this addresses the bugs that have been reported so far
// without being overly complex.
if digits_after_decimal.get() < 9 {
let trunc_num = f64_10_pow_x(digits_after_decimal);
value = f64::trunc(value * trunc_num) / trunc_num;
let int_part = value.trunc() as u64;
let frac_part =
f64::round(value.fract() * f64_10_pow_x(digits_after_decimal)) as u64;
let width = digits_before_decimal.widen::<usize>()
+ 1
+ digits_after_decimal.get().widen::<usize>();
try_likely_ok!(format_int_padded(
output,
int_part,
digits_before_decimal.widen()
));
try_likely_ok!(output.write_all(b"."));
try_likely_ok!(format_int_padded(
output,
frac_part,
digits_after_decimal.get().widen()
));
Ok(width)
} else {
// For precision >= 9, use write! to avoid off-by-one errors from floating point
// rounding (see #724). Integer extraction of the fractional part could overflow
// the digit count when rounding causes a carry.
let digits_after = digits_after_decimal.get().widen::<usize>();
let width = digits_before_decimal.widen::<usize>() + 1 + digits_after;
try_likely_ok!(write!(output, "{value:0>width$.digits_after$}"));
Ok(width)
}
}
None => format_int_padded(output, value.trunc() as u64, digits_before_decimal),
}
}
/// Format a single digit.
#[inline]
pub(crate) fn format_single_digit(
output: &mut (impl io::Write + ?Sized),
value: ru8<0, 9>,
) -> io::Result<usize> {
write(output, num_fmt::single_digit(value))
}
/// Format a two digit number with the specified padding.
#[inline]
pub(crate) fn format_two_digits(
output: &mut (impl io::Write + ?Sized),
value: ru8<0, 99>,
padding: modifier::Padding,
) -> io::Result<usize> {
let s = match padding {
modifier::Padding::Space => num_fmt::two_digits_space_padded(value),
modifier::Padding::Zero => num_fmt::two_digits_zero_padded(value),
modifier::Padding::None => num_fmt::one_to_two_digits_no_padding(value),
};
write(output, s)
}
/// Format a three digit number with the specified padding.
#[inline]
pub(crate) fn format_three_digits(
output: &mut (impl io::Write + ?Sized),
value: ru16<0, 999>,
padding: modifier::Padding,
) -> io::Result<usize> {
let [first, second_and_third] = match padding {
modifier::Padding::Space => num_fmt::three_digits_space_padded(value),
modifier::Padding::Zero => num_fmt::three_digits_zero_padded(value),
modifier::Padding::None => num_fmt::one_to_three_digits_no_padding(value),
};
write_many(output, [first, second_and_third])
}
/// Format a four digit number with the specified padding.
#[inline]
pub(crate) fn format_four_digits(
output: &mut (impl io::Write + ?Sized),
value: ru16<0, 9_999>,
padding: modifier::Padding,
) -> io::Result<usize> {
let [first_and_second, third_and_fourth] = match padding {
modifier::Padding::Space => num_fmt::four_digits_space_padded(value),
modifier::Padding::Zero => num_fmt::four_digits_zero_padded(value),
modifier::Padding::None => num_fmt::one_to_four_digits_no_padding(value),
};
write_many(output, [first_and_second, third_and_fourth])
}
/// Format a four digit number that is padded with zeroes.
#[inline]
pub(crate) fn format_four_digits_pad_zero(
output: &mut (impl io::Write + ?Sized),
value: ru16<0, 9_999>,
) -> io::Result<usize> {
write_many(output, num_fmt::four_digits_zero_padded(value))
}
/// Format a five digit number that is padded with zeroes.
#[inline]
pub(crate) fn format_five_digits_pad_zero(
output: &mut (impl io::Write + ?Sized),
value: ru32<0, 99_999>,
) -> io::Result<usize> {
write_many(output, num_fmt::five_digits_zero_padded(value))
}
/// Format a six digit number that is padded with zeroes.
#[inline]
pub(crate) fn format_six_digits_pad_zero(
output: &mut (impl io::Write + ?Sized),
value: ru32<0, 999_999>,
) -> io::Result<usize> {
write_many(output, num_fmt::six_digits_zero_padded(value))
}
/// Format a number with no padding.
///
/// If the sign is mandatory, the sign must be written by the caller.
#[inline]
pub(crate) fn format_u64_pad_none(
output: &mut (impl io::Write + ?Sized),
value: u64,
) -> io::Result<usize> {
write(output, &num_fmt::u64_pad_none(value))
}
/// Format a number with no padding.
///
/// If the sign is mandatory, the sign must be written by the caller.
#[inline]
pub(crate) fn format_u128_pad_none(
output: &mut (impl io::Write + ?Sized),
value: u128,
) -> io::Result<usize> {
write(output, &num_fmt::u128_pad_none(value))
}
/// Format the day into the designated output.
#[inline]
fn fmt_day(
output: &mut (impl io::Write + ?Sized),
day: Day,
modifier::Day { padding }: modifier::Day,
) -> Result<usize, io::Error> {
format_two_digits(output, day.expand(), padding)
}
/// Format the month into the designated output using the abbreviated name.
#[inline]
fn fmt_month_short(
output: &mut (impl io::Write + ?Sized),
month: Month,
modifier::MonthShort {
case_sensitive: _, // no effect on formatting
}: modifier::MonthShort,
) -> io::Result<usize> {
// Safety: All month names are at least three bytes long.
write(output, unsafe {
MONTH_NAMES[u8::from(month).widen::<usize>() - 1].get_unchecked(..3)
})
}
/// Format the month into the designated output using the full name.
#[inline]
fn fmt_month_long(
output: &mut (impl io::Write + ?Sized),
month: Month,
modifier::MonthLong {
case_sensitive: _, // no effect on formatting
}: modifier::MonthLong,
) -> io::Result<usize> {
write(output, MONTH_NAMES[u8::from(month).widen::<usize>() - 1])
}
/// Format the month into the designated output as a number from 1-12.
#[inline]
fn fmt_month_numerical(
output: &mut (impl io::Write + ?Sized),
month: Month,
modifier::MonthNumerical { padding }: modifier::MonthNumerical,
) -> io::Result<usize> {
format_two_digits(
output,
// Safety: The month is guaranteed to be in the range `1..=12`.
unsafe { ru8::new_unchecked(u8::from(month)) },
padding,
)
}
/// Format the ordinal into the designated output.
#[inline]
fn fmt_ordinal(
output: &mut (impl io::Write + ?Sized),
ordinal: Ordinal,
modifier::Ordinal { padding }: modifier::Ordinal,
) -> Result<usize, io::Error> {
format_three_digits(output, ordinal.expand(), padding)
}
/// Format the weekday into the designated output using the abbreviated name.
#[inline]
fn fmt_weekday_short(
output: &mut (impl io::Write + ?Sized),
weekday: Weekday,
modifier::WeekdayShort {
case_sensitive: _, // no effect on formatting
}: modifier::WeekdayShort,
) -> io::Result<usize> {
// Safety: All weekday names are at least three bytes long.
write(output, unsafe {
WEEKDAY_NAMES[weekday.number_days_from_monday().widen::<usize>()].get_unchecked(..3)
})
}
/// Format the weekday into the designated output using the full name.
#[inline]
fn fmt_weekday_long(
output: &mut (impl io::Write + ?Sized),
weekday: Weekday,
modifier::WeekdayLong {
case_sensitive: _, // no effect on formatting
}: modifier::WeekdayLong,
) -> io::Result<usize> {
write(
output,
WEEKDAY_NAMES[weekday.number_days_from_monday().widen::<usize>()],
)
}
/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
/// the modifier), where Sunday is either 0 or 1.
#[inline]
fn fmt_weekday_sunday(
output: &mut (impl io::Write + ?Sized),
weekday: Weekday,
modifier::WeekdaySunday { one_indexed }: modifier::WeekdaySunday,
) -> io::Result<usize> {
// Safety: The value is guaranteed to be in the range `0..=7`.
format_single_digit(output, unsafe {
ru8::new_unchecked(weekday.number_days_from_sunday() + u8::from(one_indexed))
})
}
/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
/// the modifier), where Monday is either 0 or 1.
#[inline]
fn fmt_weekday_monday(
output: &mut (impl io::Write + ?Sized),
weekday: Weekday,
modifier::WeekdayMonday { one_indexed }: modifier::WeekdayMonday,
) -> io::Result<usize> {
// Safety: The value is guaranteed to be in the range `0..=7`.
format_single_digit(output, unsafe {
ru8::new_unchecked(weekday.number_days_from_monday() + u8::from(one_indexed))
})
}
#[inline]
fn fmt_week_number_iso(
output: &mut (impl io::Write + ?Sized),
week_number: IsoWeekNumber,
modifier::WeekNumberIso { padding }: modifier::WeekNumberIso,
) -> io::Result<usize> {
format_two_digits(output, week_number.expand(), padding)
}
#[inline]
fn fmt_week_number_sunday(
output: &mut (impl io::Write + ?Sized),
week_number: SundayBasedWeek,
modifier::WeekNumberSunday { padding }: modifier::WeekNumberSunday,
) -> io::Result<usize> {
format_two_digits(output, week_number.expand(), padding)
}
#[inline]
fn fmt_week_number_monday(
output: &mut (impl io::Write + ?Sized),
week_number: MondayBasedWeek,
modifier::WeekNumberMonday { padding }: modifier::WeekNumberMonday,
) -> io::Result<usize> {
format_two_digits(output, week_number.expand(), padding)
}
#[inline]
fn fmt_calendar_year_full_extended_range(
output: &mut (impl io::Write + ?Sized),
full_year: Year,
modifier::CalendarYearFullExtendedRange {
padding,
sign_is_mandatory,
}: modifier::CalendarYearFullExtendedRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(
output,
full_year.is_negative(),
sign_is_mandatory || full_year.get() >= 10_000
));
// Safety: We just called `.abs()`, so zero is the minimum. The maximum is
// unchanged.
let value: ru32<0, 999_999> =
unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
bytes += if let Some(value) = value.narrow::<0, 9_999>() {
try_likely_ok!(format_four_digits(output, value.into(), padding))
} else if let Some(value) = value.narrow::<0, 99_999>() {
try_likely_ok!(format_five_digits_pad_zero(output, value))
} else {
try_likely_ok!(format_six_digits_pad_zero(output, value))
};
Ok(bytes)
}
#[inline]
fn fmt_calendar_year_full_standard_range(
output: &mut (impl io::Write + ?Sized),
full_year: StandardYear,
modifier::CalendarYearFullStandardRange {
padding,
sign_is_mandatory,
}: modifier::CalendarYearFullStandardRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, full_year.is_negative(), sign_is_mandatory));
// Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
bytes += try_likely_ok!(format_four_digits(
output,
unsafe { full_year.abs().narrow_unchecked::<0, 9_999>().into() },
padding
));
Ok(bytes)
}
#[inline]
fn fmt_iso_year_full_extended_range(
output: &mut (impl io::Write + ?Sized),
full_year: Year,
modifier::IsoYearFullExtendedRange {
padding,
sign_is_mandatory,
}: modifier::IsoYearFullExtendedRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(
output,
full_year.is_negative(),
sign_is_mandatory || full_year.get() >= 10_000,
));
// Safety: The minimum is zero due to the `.abs()` call, with the maximum is unchanged.
let value: ru32<0, 999_999> =
unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
bytes += if let Some(value) = value.narrow::<0, 9_999>() {
try_likely_ok!(format_four_digits(output, value.into(), padding))
} else if let Some(value) = value.narrow::<0, 99_999>() {
try_likely_ok!(format_five_digits_pad_zero(output, value))
} else {
try_likely_ok!(format_six_digits_pad_zero(output, value))
};
Ok(bytes)
}
#[inline]
fn fmt_iso_year_full_standard_range(
output: &mut (impl io::Write + ?Sized),
year: StandardYear,
modifier::IsoYearFullStandardRange {
padding,
sign_is_mandatory,
}: modifier::IsoYearFullStandardRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, year.is_negative(), sign_is_mandatory));
// Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
bytes += try_likely_ok!(format_four_digits(
output,
unsafe { year.abs().narrow_unchecked::<0, 9_999>().into() },
padding
));
Ok(bytes)
}
#[inline]
fn fmt_calendar_year_century_extended_range(
output: &mut (impl io::Write + ?Sized),
century: ExtendedCentury,
is_negative: bool,
modifier::CalendarYearCenturyExtendedRange {
padding,
sign_is_mandatory,
}: modifier::CalendarYearCenturyExtendedRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(
output,
is_negative,
sign_is_mandatory || century.get() >= 100
));
// Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
bytes += if let Some(century) = century.narrow::<0, 99>() {
try_likely_ok!(format_two_digits(output, century.into(), padding))
} else if let Some(century) = century.narrow::<0, 999>() {
try_likely_ok!(format_three_digits(output, century, padding))
} else {
try_likely_ok!(format_four_digits(output, century, padding))
};
Ok(bytes)
}
#[inline]
fn fmt_calendar_year_century_standard_range(
output: &mut (impl io::Write + ?Sized),
century: StandardCentury,
is_negative: bool,
modifier::CalendarYearCenturyStandardRange {
padding,
sign_is_mandatory,
}: modifier::CalendarYearCenturyStandardRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
// Safety: The minimum is zero due to the `.unsigned_abs()` call.
let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
Ok(bytes)
}
#[inline]
fn fmt_iso_year_century_extended_range(
output: &mut (impl io::Write + ?Sized),
century: ExtendedCentury,
is_negative: bool,
modifier::IsoYearCenturyExtendedRange {
padding,
sign_is_mandatory,
}: modifier::IsoYearCenturyExtendedRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(
output,
is_negative,
sign_is_mandatory || century.get() >= 100,
));
// Safety: The minimum is zero due to the `.unsigned_abs()` call, with the maximum is unchanged.
let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
bytes += if let Some(century) = century.narrow::<0, 99>() {
try_likely_ok!(format_two_digits(output, century.into(), padding))
} else if let Some(century) = century.narrow::<0, 999>() {
try_likely_ok!(format_three_digits(output, century, padding))
} else {
try_likely_ok!(format_four_digits(output, century, padding))
};
Ok(bytes)
}
#[inline]
fn fmt_iso_year_century_standard_range(
output: &mut (impl io::Write + ?Sized),
century: StandardCentury,
is_negative: bool,
modifier::IsoYearCenturyStandardRange {
padding,
sign_is_mandatory,
}: modifier::IsoYearCenturyStandardRange,
) -> io::Result<usize> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
// Safety: The minimum is zero due to the `.unsigned_abs()` call.
let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
Ok(bytes)
}
#[inline]
fn fmt_calendar_year_last_two(
output: &mut (impl io::Write + ?Sized),
last_two: LastTwo,
modifier::CalendarYearLastTwo { padding }: modifier::CalendarYearLastTwo,
) -> io::Result<usize> {
format_two_digits(output, last_two, padding)
}
#[inline]
fn fmt_iso_year_last_two(
output: &mut (impl io::Write + ?Sized),
last_two: LastTwo,
modifier::IsoYearLastTwo { padding }: modifier::IsoYearLastTwo,
) -> io::Result<usize> {
format_two_digits(output, last_two, padding)
}
/// Format the hour into the designated output using the 12-hour clock.
#[inline]
fn fmt_hour_12(
output: &mut (impl io::Write + ?Sized),
hour: Hours,
modifier::Hour12 { padding }: modifier::Hour12,
) -> io::Result<usize> {
// Safety: The value is guaranteed to be in the range `1..=12`.
format_two_digits(
output,
unsafe { ru8::new_unchecked((hour.get() + 11) % 12 + 1) },
padding,
)
}
/// Format the hour into the designated output using the 24-hour clock.
#[inline]
fn fmt_hour_24(
output: &mut (impl io::Write + ?Sized),
hour: Hours,
modifier::Hour24 { padding }: modifier::Hour24,
) -> io::Result<usize> {
format_two_digits(output, hour.expand(), padding)
}
/// Format the minute into the designated output.
#[inline]
fn fmt_minute(
output: &mut (impl io::Write + ?Sized),
minute: Minutes,
modifier::Minute { padding }: modifier::Minute,
) -> Result<usize, io::Error> {
format_two_digits(output, minute.expand(), padding)
}
/// Format the period into the designated output.
#[inline]
fn fmt_period(
output: &mut (impl io::Write + ?Sized),
period: Period,
modifier::Period {
is_uppercase,
case_sensitive: _, // no effect on formatting
}: modifier::Period,
) -> Result<usize, io::Error> {
write(
output,
match (period, is_uppercase) {
(Period::Am, false) => "am",
(Period::Am, true) => "AM",
(Period::Pm, false) => "pm",
(Period::Pm, true) => "PM",
},
)
}
/// Format the second into the designated output.
#[inline]
fn fmt_second(
output: &mut (impl io::Write + ?Sized),
second: Seconds,
modifier::Second { padding }: modifier::Second,
) -> Result<usize, io::Error> {
format_two_digits(output, second.expand(), padding)
}
/// Format the subsecond into the designated output.
#[inline]
fn fmt_subsecond(
output: &mut (impl io::Write + ?Sized),
nanos: Nanoseconds,
modifier::Subsecond { digits }: modifier::Subsecond,
) -> Result<usize, io::Error> {
use modifier::SubsecondDigits::*;
#[repr(C, align(8))]
#[derive(Clone, Copy)]
struct Digits {
_padding: MaybeUninit<[u8; 7]>,
digit_1: u8,
digits_2_thru_9: [u8; 8],
}
let [
digit_1,
digits_2_and_3,
digits_4_and_5,
digits_6_and_7,
digits_8_and_9,
] = num_fmt::subsecond_from_nanos(nanos);
// Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows the
// conversion to a `u64` to be zero cost, resulting in a nontrivial performance improvement.
let buf = Digits {
_padding: MaybeUninit::uninit(),
digit_1: digit_1.as_bytes()[0],
digits_2_thru_9: [
digits_2_and_3.as_bytes()[0],
digits_2_and_3.as_bytes()[1],
digits_4_and_5.as_bytes()[0],
digits_4_and_5.as_bytes()[1],
digits_6_and_7.as_bytes()[0],
digits_6_and_7.as_bytes()[1],
digits_8_and_9.as_bytes()[0],
digits_8_and_9.as_bytes()[1],
],
};
let len = match digits {
One => 1,
Two => 2,
Three => 3,
Four => 4,
Five => 5,
Six => 6,
Seven => 7,
Eight => 8,
Nine => 9,
OneOrMore => {
// By converting the bytes into a single integer, we can effectively perform an equality
// check against b'0' for all bytes at once. This is actually faster than
// using portable SIMD (even with `-Ctarget-cpu=native`).
let bitmask = u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
let digits_to_truncate = bitmask.leading_zeros() / 8;
9 - digits_to_truncate as usize
}
};
// Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of bytes
// we wish to display (that is between 1 and 9 inclusive). `Digits` is `#[repr(C)]`, so the
// layout is guaranteed.
let s = unsafe {
num_fmt::StackStr::new(
*(&raw const buf)
.byte_add(core::mem::offset_of!(Digits, digit_1))
.cast::<[MaybeUninit<u8>; 9]>(),
len,
)
};
write(output, &s)
}
#[inline]
fn fmt_sign(
output: &mut (impl io::Write + ?Sized),
is_negative: bool,
sign_is_mandatory: bool,
) -> Result<usize, io::Error> {
if is_negative {
write(output, "-")
} else if sign_is_mandatory {
write(output, "+")
} else {
Ok(0)
}
}
/// Format the offset hour into the designated output.
#[inline]
fn fmt_offset_hour(
output: &mut (impl io::Write + ?Sized),
is_negative: bool,
hour: OffsetHours,
modifier::OffsetHour {
padding,
sign_is_mandatory,
}: modifier::OffsetHour,
) -> Result<usize, io::Error> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
// Safety: The value is guaranteed to be under 100 because of `OffsetHours`.
bytes += try_likely_ok!(format_two_digits(
output,
unsafe { ru8::new_unchecked(hour.get().unsigned_abs()) },
padding,
));
Ok(bytes)
}
/// Format the offset minute into the designated output.
#[inline]
fn fmt_offset_minute(
output: &mut (impl io::Write + ?Sized),
offset_minute: OffsetMinutes,
modifier::OffsetMinute { padding }: modifier::OffsetMinute,
) -> Result<usize, io::Error> {
format_two_digits(
output,
// Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
// value is guaranteed to be in the range `0..=59`.
unsafe { ru8::new_unchecked(offset_minute.get().unsigned_abs()) },
padding,
)
}
/// Format the offset second into the designated output.
#[inline]
fn fmt_offset_second(
output: &mut (impl io::Write + ?Sized),
offset_second: OffsetSeconds,
modifier::OffsetSecond { padding }: modifier::OffsetSecond,
) -> Result<usize, io::Error> {
format_two_digits(
output,
// Safety: `OffsetSeconds` is guaranteed to be in the range `-59..=59`, so the absolute
// value is guaranteed to be in the range `0..=59`.
unsafe { ru8::new_unchecked(offset_second.get().unsigned_abs()) },
padding,
)
}
/// Format the Unix timestamp (in seconds) into the designated output.
#[inline]
fn fmt_unix_timestamp_second(
output: &mut (impl io::Write + ?Sized),
timestamp: i64,
modifier::UnixTimestampSecond { sign_is_mandatory }: modifier::UnixTimestampSecond,
) -> Result<usize, io::Error> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, timestamp < 0, sign_is_mandatory));
bytes += try_likely_ok!(format_u64_pad_none(output, timestamp.unsigned_abs()));
Ok(bytes)
}
/// Format the Unix timestamp (in milliseconds) into the designated output.
#[inline]
fn fmt_unix_timestamp_millisecond(
output: &mut (impl io::Write + ?Sized),
timestamp_millis: i64,
modifier::UnixTimestampMillisecond { sign_is_mandatory }: modifier::UnixTimestampMillisecond,
) -> Result<usize, io::Error> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, timestamp_millis < 0, sign_is_mandatory));
bytes += try_likely_ok!(format_u64_pad_none(output, timestamp_millis.unsigned_abs()));
Ok(bytes)
}
/// Format the Unix timestamp (in microseconds) into the designated output.
#[inline]
fn fmt_unix_timestamp_microsecond(
output: &mut (impl io::Write + ?Sized),
timestamp_micros: i128,
modifier::UnixTimestampMicrosecond { sign_is_mandatory }: modifier::UnixTimestampMicrosecond,
) -> Result<usize, io::Error> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, timestamp_micros < 0, sign_is_mandatory));
bytes += try_likely_ok!(format_u128_pad_none(
output,
timestamp_micros.unsigned_abs()
));
Ok(bytes)
}
/// Format the Unix timestamp (in nanoseconds) into the designated output.
#[inline]
fn fmt_unix_timestamp_nanosecond(
output: &mut (impl io::Write + ?Sized),
timestamp_nanos: i128,
modifier::UnixTimestampNanosecond { sign_is_mandatory }: modifier::UnixTimestampNanosecond,
) -> Result<usize, io::Error> {
let mut bytes = 0;
bytes += try_likely_ok!(fmt_sign(output, timestamp_nanos < 0, sign_is_mandatory));
bytes += try_likely_ok!(format_u128_pad_none(output, timestamp_nanos.unsigned_abs()));
Ok(bytes)
}
+24
View File
@@ -0,0 +1,24 @@
//! Hints to the compiler that affects how code should be emitted or optimized.
/// Indicate that a given branch is **not** likely to be taken, relatively speaking.
#[inline(always)]
#[cold]
pub(crate) const fn cold_path() {}
/// Indicate that a given condition is likely to be true.
#[inline(always)]
pub(crate) const fn likely(b: bool) -> bool {
if !b {
cold_path();
}
b
}
/// Indicate that a given condition is likely to be false.
#[inline(always)]
pub(crate) const fn unlikely(b: bool) -> bool {
if b {
cold_path();
}
b
}
+381
View File
@@ -0,0 +1,381 @@
//! The [`Instant`] struct and its associated `impl`s.
#![expect(deprecated)]
use core::borrow::Borrow;
use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration as StdDuration;
use std::time::Instant as StdInstant;
use crate::SignedDuration;
/// A measurement of a monotonically non-decreasing clock. Opaque and useful only with
/// [`SignedDuration`].
///
/// Instants are always guaranteed to be no less than any previously measured instant when created,
/// and are often useful for tasks such as measuring benchmarks or timing how long an operation
/// takes.
///
/// Note, however, that instants are not guaranteed to be **steady**. In other words, each tick of
/// the underlying clock may not be the same length (e.g. some seconds may be longer than others).
/// An instant may jump forwards or experience time dilation (slow down or speed up), but it will
/// never go backwards.
///
/// Instants are opaque types that can only be compared to one another. There is no method to get
/// "the number of seconds" from an instant. Instead, it only allows measuring the duration between
/// two instants (or comparing two instants).
///
/// This implementation allows for operations with signed [`SignedDuration`]s, but is otherwise
/// identical to [`std::time::Instant`].
#[doc(hidden)]
#[deprecated(
since = "0.3.35",
note = "import `std::time::Instant` and `time::ext::InstantExt` instead"
)]
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(pub StdInstant);
impl Instant {
/// Returns an `Instant` corresponding to "now".
///
/// ```rust
/// # #![expect(deprecated)]
/// # use time::Instant;
/// println!("{:?}", Instant::now());
/// ```
#[inline]
pub fn now() -> Self {
Self(StdInstant::now())
}
/// Returns the amount of time elapsed since this instant was created. The duration will always
/// be nonnegative if the instant is not synthetically created.
///
/// ```rust
/// # #![expect(deprecated)]
/// # use time::{Instant, ext::{NumericalStdDuration, NumericalDuration}};
/// # use std::thread;
/// let instant = Instant::now();
/// thread::sleep(1.std_milliseconds());
/// assert!(instant.elapsed() >= 1.milliseconds());
/// ```
#[inline]
pub fn elapsed(self) -> SignedDuration {
Self::now() - self
}
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
///
/// ```rust
/// # #![expect(deprecated)]
/// # use time::{Instant, ext::NumericalDuration};
/// let now = Instant::now();
/// assert_eq!(now.checked_add(5.seconds()), Some(now + 5.seconds()));
/// assert_eq!(now.checked_add((-5).seconds()), Some(now + (-5).seconds()));
/// ```
#[inline]
pub fn checked_add(self, duration: SignedDuration) -> Option<Self> {
if duration.is_zero() {
Some(self)
} else if duration.is_positive() {
self.0.checked_add(duration.unsigned_abs()).map(Self)
} else {
debug_assert!(duration.is_negative());
self.0.checked_sub(duration.unsigned_abs()).map(Self)
}
}
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
///
/// ```rust
/// # #![expect(deprecated)]
/// # use time::{Instant, ext::NumericalDuration};
/// let now = Instant::now();
/// assert_eq!(now.checked_sub(5.seconds()), Some(now - 5.seconds()));
/// assert_eq!(now.checked_sub((-5).seconds()), Some(now - (-5).seconds()));
/// ```
#[inline]
pub fn checked_sub(self, duration: SignedDuration) -> Option<Self> {
if duration.is_zero() {
Some(self)
} else if duration.is_positive() {
self.0.checked_sub(duration.unsigned_abs()).map(Self)
} else {
debug_assert!(duration.is_negative());
self.0.checked_add(duration.unsigned_abs()).map(Self)
}
}
/// Obtain the inner [`std::time::Instant`].
///
/// ```rust
/// # #![expect(deprecated)]
/// # use time::Instant;
/// let now = Instant::now();
/// assert_eq!(now.into_inner(), now.0);
/// ```
#[inline]
pub const fn into_inner(self) -> StdInstant {
self.0
}
}
impl From<StdInstant> for Instant {
#[inline]
fn from(instant: StdInstant) -> Self {
Self(instant)
}
}
impl From<Instant> for StdInstant {
#[inline]
fn from(instant: Instant) -> Self {
instant.0
}
}
impl Sub for Instant {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
fn sub(self, other: Self) -> Self::Output {
match self.0.cmp(&other.0) {
Ordering::Equal => SignedDuration::ZERO,
Ordering::Greater => (self.0 - other.0)
.try_into()
.expect("overflow converting `std::time::Duration` to `time::SignedDuration`"),
Ordering::Less => -SignedDuration::try_from(other.0 - self.0)
.expect("overflow converting `std::time::Duration` to `time::SignedDuration`"),
}
}
}
impl Sub<StdInstant> for Instant {
type Output = SignedDuration;
#[inline]
fn sub(self, other: StdInstant) -> Self::Output {
self - Self(other)
}
}
impl Sub<Instant> for StdInstant {
type Output = SignedDuration;
#[inline]
fn sub(self, other: Instant) -> Self::Output {
Instant(self) - other
}
}
impl Add<SignedDuration> for Instant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add(self, duration: SignedDuration) -> Self::Output {
if duration.is_positive() {
Self(self.0 + duration.unsigned_abs())
} else if duration.is_negative() {
#[expect(clippy::unchecked_time_subtraction)]
Self(self.0 - duration.unsigned_abs())
} else {
debug_assert!(duration.is_zero());
self
}
}
}
impl Add<SignedDuration> for StdInstant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add(self, duration: SignedDuration) -> Self::Output {
(Instant(self) + duration).0
}
}
impl Add<StdDuration> for Instant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add(self, duration: StdDuration) -> Self::Output {
Self(self.0 + duration)
}
}
impl AddAssign<SignedDuration> for Instant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl AddAssign<StdDuration> for Instant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add_assign(&mut self, rhs: StdDuration) {
*self = *self + rhs;
}
}
impl AddAssign<SignedDuration> for StdInstant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl Sub<SignedDuration> for Instant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub(self, duration: SignedDuration) -> Self::Output {
if duration.is_positive() {
#[expect(clippy::unchecked_time_subtraction)]
Self(self.0 - duration.unsigned_abs())
} else if duration.is_negative() {
Self(self.0 + duration.unsigned_abs())
} else {
debug_assert!(duration.is_zero());
self
}
}
}
impl Sub<SignedDuration> for StdInstant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub(self, duration: SignedDuration) -> Self::Output {
(Instant(self) - duration).0
}
}
impl Sub<StdDuration> for Instant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub(self, duration: StdDuration) -> Self::Output {
#[expect(clippy::unchecked_time_subtraction)]
Self(self.0 - duration)
}
}
impl SubAssign<SignedDuration> for Instant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl SubAssign<StdDuration> for Instant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub_assign(&mut self, rhs: StdDuration) {
*self = *self - rhs;
}
}
impl SubAssign<SignedDuration> for StdInstant {
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure.
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl PartialEq<StdInstant> for Instant {
#[inline]
fn eq(&self, rhs: &StdInstant) -> bool {
self.0.eq(rhs)
}
}
impl PartialEq<Instant> for StdInstant {
#[inline]
fn eq(&self, rhs: &Instant) -> bool {
self.eq(&rhs.0)
}
}
impl PartialOrd<StdInstant> for Instant {
#[inline]
fn partial_cmp(&self, rhs: &StdInstant) -> Option<Ordering> {
self.0.partial_cmp(rhs)
}
}
impl PartialOrd<Instant> for StdInstant {
#[inline]
fn partial_cmp(&self, rhs: &Instant) -> Option<Ordering> {
self.partial_cmp(&rhs.0)
}
}
impl AsRef<StdInstant> for Instant {
#[inline]
fn as_ref(&self) -> &StdInstant {
&self.0
}
}
impl Borrow<StdInstant> for Instant {
#[inline]
fn borrow(&self) -> &StdInstant {
&self.0
}
}
+232
View File
@@ -0,0 +1,232 @@
//! Macros for use within the library. They are not publicly available.
/// Division of integers, rounding the resulting value towards negative infinity.
macro_rules! div_floor {
($self:expr, $rhs:expr) => {
match ($self, $rhs) {
(this, rhs) => {
let d = this / rhs;
let r = this % rhs;
// If the remainder is non-zero, we need to subtract one if the
// signs of self and rhs differ, as this means we rounded upwards
// instead of downwards. We do this branchlessly by creating a mask
// which is all-ones iff the signs differ, and 0 otherwise. Then by
// adding this mask (which corresponds to the signed value -1), we
// get our correction.
let correction = (this ^ rhs) >> (size_of_val(&this) * 8 - 1);
if r != 0 { d + correction } else { d }
}
}
};
}
/// Similar to `overflowing_add`, but returning the number of times that it overflowed. Contained to
/// a certain range and only overflows a maximum number of times.
macro_rules! carry {
(@most_once $value:expr, $min:literal.. $max:expr) => {
match ($value, $min, $max) {
(value, min, max) => {
if crate::hint::likely(value >= min) {
if crate::hint::likely(value < max) {
(value, 0)
} else {
(value - (max - min), 1)
}
} else {
(value + (max - min), -1)
}
}
}
};
(@most_twice $value:expr, $min:literal.. $max:expr) => {
match ($value, $min, $max) {
(value, min, max) => {
if crate::hint::likely(value >= min) {
if crate::hint::likely(value < max) {
(value, 0)
} else if value < 2 * max - min {
(value - (max - min), 1)
} else {
(value - 2 * (max - min), 2)
}
} else {
if value >= min - max {
(value + (max - min), -1)
} else {
(value + 2 * (max - min), -2)
}
}
}
}
};
(@most_thrice $value:expr, $min:literal.. $max:expr) => {
match ($value, $min, $max) {
(value, min, max) => {
if crate::hint::likely(value >= min) {
if crate::hint::likely(value < max) {
(value, 0)
} else if value < 2 * max - min {
(value - (max - min), 1)
} else if value < 3 * max - 2 * min {
(value - 2 * (max - min), 2)
} else {
(value - 3 * (max - min), 3)
}
} else {
if value >= min - max {
(value + (max - min), -1)
} else if value >= 2 * (min - max) {
(value + 2 * (max - min), -2)
} else {
(value + 3 * (max - min), -3)
}
}
}
}
};
}
/// 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 crate::hint::unlikely($from >= max) {
$from -= max - min;
$to += 1;
} else if crate::hint::unlikely($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);
let days_in_year = crate::util::range_validated::days_in_year($year).cast_signed();
#[allow(unused_assignments)]
if crate::hint::unlikely($ordinal > days_in_year) {
$ordinal -= days_in_year;
$year += 1;
} else if crate::hint::unlikely($ordinal < 1) {
$year -= 1;
$ordinal += crate::util::range_validated::days_in_year($year).cast_signed();
}
};
}
/// Constructs a ranged integer, returning a `ComponentRange` error if the value is out of range.
macro_rules! ensure_ranged {
($type:ty : $value:ident) => {
match <$type>::new($value) {
Some(val) => val,
None => {
$crate::hint::cold_path();
return Err(crate::error::ComponentRange::unconditional(stringify!($value)));
}
}
};
($type:ty : $value:ident ($name:literal)) => {
match <$type>::new($value) {
Some(val) => val,
None => {
$crate::hint::cold_path();
return Err(crate::error::ComponentRange::unconditional($name));
}
}
};
($type:ty : $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 => {
$crate::hint::cold_path();
return Err(crate::error::ComponentRange::unconditional(stringify!($value)));
}
},
None => {
$crate::hint::cold_path();
return Err(crate::error::ComponentRange::unconditional(stringify!($value)));
}
}
};
}
/// 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) => {
$crate::hint::cold_path();
return Err(error);
}
}
};
}
/// Try to unwrap an expression, returning if not possible.
///
/// This is identical to `?` in terms of behavior, but marks the error path as cold.
#[cfg(any(feature = "formatting", feature = "parsing"))]
macro_rules! try_likely_ok {
($e:expr) => {
match $e {
Ok(value) => value,
Err(error) => {
$crate::hint::cold_path();
return Err(error.into());
}
}
};
}
/// 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 => {
$crate::hint::cold_path();
return None;
}
}
};
}
/// `unreachable!()`, but better.
macro_rules! bug {
() => {
compile_error!("provide an error message to help fix a possible bug")
};
($descr:literal) => {{
$crate::hint::cold_path();
panic!(concat!("internal error: ", $descr))
}};
}
pub(crate) use bug;
pub(crate) use carry;
pub(crate) use cascade;
pub(crate) use const_try;
pub(crate) use const_try_opt;
pub(crate) use div_floor;
pub(crate) use ensure_ranged;
#[cfg(any(feature = "formatting", feature = "parsing"))]
pub(crate) use try_likely_ok;
@@ -0,0 +1,24 @@
use crate::OffsetDateTime;
use crate::unit::*;
impl From<js_sys::Date> for OffsetDateTime {
/// # Panics
///
/// This may panic if the timestamp can not be represented.
#[track_caller]
fn from(js_date: js_sys::Date) -> Self {
// get_time() returns milliseconds
let timestamp_nanos = js_date.get_time() as i128 * Nanosecond::per_t::<i128>(Millisecond);
Self::from_unix_timestamp_nanos(timestamp_nanos)
.expect("invalid timestamp: Timestamp cannot fit in range")
}
}
impl From<OffsetDateTime> for js_sys::Date {
fn from(datetime: OffsetDateTime) -> Self {
// new Date() takes milliseconds
let timestamp =
(datetime.unix_timestamp_nanos() / Nanosecond::per_t::<i128>(Millisecond)) as f64;
Self::new(&timestamp.into())
}
}
@@ -0,0 +1,21 @@
use crate::Timestamp;
impl From<js_sys::Date> for Timestamp {
/// # Panics
///
/// This may panic if the timestamp can not be represented.
fn from(js_date: js_sys::Date) -> Self {
// get_time() returns milliseconds
let timestamp_millis = js_date.get_time() as i64;
Self::from_milliseconds(timestamp_millis)
.expect("invalid timestamp: Timestamp cannot fit in range")
}
}
impl From<Timestamp> for js_sys::Date {
fn from(datetime: Timestamp) -> Self {
// new Date() takes milliseconds
let timestamp = datetime.as_milliseconds() as f64;
Self::new(&timestamp.into())
}
}
@@ -0,0 +1,24 @@
use crate::UtcDateTime;
use crate::unit::*;
impl From<js_sys::Date> for UtcDateTime {
/// # Panics
///
/// This may panic if the timestamp can not be represented.
#[track_caller]
fn from(js_date: js_sys::Date) -> Self {
// get_time() returns milliseconds
let timestamp_nanos = (js_date.get_time() * Nanosecond::per_t::<f64>(Millisecond)) as i128;
Self::from_unix_timestamp_nanos(timestamp_nanos)
.expect("invalid timestamp: Timestamp cannot fit in range")
}
}
impl From<UtcDateTime> for js_sys::Date {
fn from(datetime: UtcDateTime) -> Self {
// new Date() takes milliseconds
let timestamp =
(datetime.unix_timestamp_nanos() / Nanosecond::per_t::<i128>(Millisecond)) as f64;
Self::new(&timestamp.into())
}
}
+38
View File
@@ -0,0 +1,38 @@
//! Comparison, arithmetic, and conversion between various types in `time` and the standard library.
//!
//! Currently, full interoperability is present between [`OffsetDateTime`](crate::OffsetDateTime),
//! [`Timestamp`](crate::Timestamp), [`UtcDateTime`](crate::UtcDateTime), and
//! [`SystemTime`](std::time::SystemTime). Partial interoperability is present with
//! [`js_sys::Date`]. Note that [`PlainDateTime`](crate::PlainDateTime) is not interoperable with
//! any of these types due to the lack of an associated UTC offset.
// Module names should have the two types sorted in alphabetical order. This avoids any question
// of which type should be the "primary" type in the module name.
#[cfg(all(
target_family = "wasm",
not(any(target_os = "emscripten", target_os = "wasi")),
feature = "wasm-bindgen"
))]
mod js_sys_date_offsetdatetime;
#[cfg(all(
target_family = "wasm",
not(any(target_os = "emscripten", target_os = "wasi")),
feature = "wasm-bindgen"
))]
mod js_sys_date_timestamp;
#[cfg(all(
target_family = "wasm",
not(any(target_os = "emscripten", target_os = "wasi")),
feature = "wasm-bindgen"
))]
mod js_sys_date_utcdatetime;
#[cfg(feature = "std")]
mod offsetdatetime_systemtime;
mod offsetdatetime_timestamp;
mod offsetdatetime_utcdatetime;
#[cfg(feature = "std")]
mod systemdatetime_timestamp;
mod timestamp_utcdatetime;
#[cfg(feature = "std")]
mod utcdatetime_systemtime;
@@ -0,0 +1,86 @@
use core::cmp::Ordering;
use core::ops::Sub;
use std::time::SystemTime;
use crate::{OffsetDateTime, SignedDuration};
impl Sub<SystemTime> for OffsetDateTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
fn sub(self, rhs: SystemTime) -> Self::Output {
self - Self::from(rhs)
}
}
impl Sub<OffsetDateTime> for SystemTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
fn sub(self, rhs: OffsetDateTime) -> Self::Output {
OffsetDateTime::from(self) - rhs
}
}
impl PartialEq<SystemTime> for OffsetDateTime {
#[inline]
fn eq(&self, rhs: &SystemTime) -> bool {
self == &Self::from(*rhs)
}
}
impl PartialEq<OffsetDateTime> for SystemTime {
#[inline]
fn eq(&self, rhs: &OffsetDateTime) -> bool {
&OffsetDateTime::from(*self) == rhs
}
}
impl PartialOrd<SystemTime> for OffsetDateTime {
#[inline]
fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
self.partial_cmp(&Self::from(*other))
}
}
impl PartialOrd<OffsetDateTime> for SystemTime {
#[inline]
fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
OffsetDateTime::from(*self).partial_cmp(other)
}
}
impl From<SystemTime> for OffsetDateTime {
#[inline]
fn from(system_time: SystemTime) -> Self {
match system_time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => Self::UNIX_EPOCH + duration,
Err(err) => Self::UNIX_EPOCH - err.duration(),
}
}
}
impl From<OffsetDateTime> for SystemTime {
/// # Panics
///
/// This may panic if the resulting `SystemTime` cannot be represented.
#[inline]
fn from(datetime: OffsetDateTime) -> Self {
let duration = datetime - OffsetDateTime::UNIX_EPOCH;
if duration.is_zero() {
Self::UNIX_EPOCH
} else if duration.is_positive() {
Self::UNIX_EPOCH + duration.unsigned_abs()
} else {
debug_assert!(duration.is_negative());
Self::UNIX_EPOCH - duration.unsigned_abs()
}
}
}
@@ -0,0 +1,92 @@
use core::cmp::Ordering;
use core::ops::Sub;
#[allow(unused_imports)] // MSRV of 1.87
use num_conv::prelude::*;
use crate::{OffsetDateTime, SignedDuration, Timestamp};
impl Sub<OffsetDateTime> for Timestamp {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: OffsetDateTime) -> Self::Output {
SignedDuration::new(
self.as_seconds() - rhs.unix_timestamp(),
self.nanosecond().cast_signed() - rhs.nanosecond().cast_signed(),
)
}
}
impl Sub<Timestamp> for OffsetDateTime {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: Timestamp) -> Self::Output {
SignedDuration::new(
self.unix_timestamp() - rhs.as_seconds(),
self.nanosecond().cast_signed() - rhs.nanosecond().cast_signed(),
)
}
}
impl PartialEq<OffsetDateTime> for Timestamp {
#[expect(clippy::suspicious_operation_groupings, reason = "false positive")]
#[inline]
fn eq(&self, other: &OffsetDateTime) -> bool {
self.as_seconds() == other.unix_timestamp() && self.nanosecond() == other.nanosecond()
}
}
impl PartialEq<Timestamp> for OffsetDateTime {
#[inline]
fn eq(&self, other: &Timestamp) -> bool {
other == self
}
}
impl PartialOrd<OffsetDateTime> for Timestamp {
#[inline]
fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
(self.as_seconds(), self.nanosecond())
.partial_cmp(&(other.unix_timestamp(), other.nanosecond()))
}
}
impl PartialOrd<Timestamp> for OffsetDateTime {
#[inline]
fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl From<OffsetDateTime> for Timestamp {
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn from(datetime: OffsetDateTime) -> Self {
// Safety: Nanoseconds have the same range.
unsafe {
Self::from_seconds(datetime.unix_timestamp())
.expect("local datetime out of valid range for `Timestamp`")
.replace_nanosecond(datetime.nanosecond())
.unwrap_unchecked()
}
}
}
impl From<Timestamp> for OffsetDateTime {
#[inline]
fn from(timestamp: Timestamp) -> Self {
// Safety: The valid range of `Timestamp` is less than that of `OffsetDateTime` (due to edge
// cases of `OffsetDateTime`). Nanoseconds have the same range.
unsafe {
Self::from_unix_timestamp(timestamp.as_seconds())
.unwrap_unchecked()
.replace_nanosecond(timestamp.nanosecond())
.unwrap_unchecked()
}
}
}
@@ -0,0 +1,80 @@
use core::cmp::Ordering;
use core::ops::Sub;
use crate::{OffsetDateTime, SignedDuration, UtcDateTime};
impl Sub<OffsetDateTime> for UtcDateTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn sub(self, rhs: OffsetDateTime) -> Self::Output {
OffsetDateTime::from(self) - rhs
}
}
impl Sub<UtcDateTime> for OffsetDateTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn sub(self, rhs: UtcDateTime) -> Self::Output {
self - Self::from(rhs)
}
}
impl PartialEq<OffsetDateTime> for UtcDateTime {
#[inline]
fn eq(&self, other: &OffsetDateTime) -> bool {
OffsetDateTime::from(*self) == *other
}
}
impl PartialEq<UtcDateTime> for OffsetDateTime {
#[inline]
fn eq(&self, other: &UtcDateTime) -> bool {
*self == Self::from(*other)
}
}
impl PartialOrd<OffsetDateTime> for UtcDateTime {
#[inline]
fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
OffsetDateTime::from(*self).partial_cmp(other)
}
}
impl PartialOrd<UtcDateTime> for OffsetDateTime {
#[inline]
fn partial_cmp(&self, other: &UtcDateTime) -> Option<Ordering> {
self.partial_cmp(&Self::from(*other))
}
}
impl From<OffsetDateTime> for UtcDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn from(datetime: OffsetDateTime) -> Self {
datetime.to_utc()
}
}
impl From<UtcDateTime> for OffsetDateTime {
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn from(datetime: UtcDateTime) -> Self {
datetime.as_plain().assume_utc()
}
}
@@ -0,0 +1,120 @@
use core::cmp::Ordering;
use core::ops::Sub;
use std::time::SystemTime;
#[allow(unused_imports)] // MSRV of 1.87
use num_conv::prelude::*;
use crate::ext::SystemTimeExt;
use crate::unit::*;
use crate::{SignedDuration, Timestamp};
impl Sub<SystemTime> for Timestamp {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: SystemTime) -> Self::Output {
let lhs_duration = self - Self::UNIX_EPOCH;
match rhs.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => lhs_duration - duration,
Err(err) => lhs_duration + err.duration(),
}
}
}
impl Sub<Timestamp> for SystemTime {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: Timestamp) -> Self::Output {
let rhs_duration = rhs - Timestamp::UNIX_EPOCH;
match self.duration_since(Self::UNIX_EPOCH) {
Ok(lhs) => lhs - rhs_duration,
Err(err) => -(err.duration() + rhs_duration),
}
}
}
impl PartialEq<SystemTime> for Timestamp {
#[inline]
fn eq(&self, other: &SystemTime) -> bool {
*self - Self::UNIX_EPOCH == other.signed_duration_since(SystemTime::UNIX_EPOCH)
}
}
impl PartialEq<Timestamp> for SystemTime {
#[inline]
fn eq(&self, other: &Timestamp) -> bool {
other == self
}
}
impl PartialOrd<SystemTime> for Timestamp {
#[inline]
fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
(*self - Self::UNIX_EPOCH).partial_cmp(&other.signed_duration_since(SystemTime::UNIX_EPOCH))
}
}
impl PartialOrd<Timestamp> for SystemTime {
#[inline]
fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl From<SystemTime> for Timestamp {
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn from(datetime: SystemTime) -> Self {
let duration = datetime.signed_duration_since(SystemTime::UNIX_EPOCH);
let mut seconds = duration.whole_seconds();
let mut nanoseconds = duration.subsec_nanoseconds();
if nanoseconds < 0 {
seconds -= 1;
nanoseconds += Nanosecond::per_t::<i32>(Second);
}
Self::new(seconds, nanoseconds.cast_unsigned())
.expect("SystemTime value is out of valid range")
}
}
impl From<Timestamp> for SystemTime {
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn from(timestamp: Timestamp) -> Self {
Self::UNIX_EPOCH + (timestamp - Timestamp::UNIX_EPOCH)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ext::NumericalDuration;
#[test]
fn systime_to_timestamp() -> crate::Result<()> {
let ts = Timestamp::from(SystemTime::UNIX_EPOCH + 5.seconds());
assert_eq!(ts, Timestamp::from_seconds(5)?);
let ts = Timestamp::from(SystemTime::UNIX_EPOCH - 5.seconds());
assert_eq!(ts, Timestamp::from_seconds(-5)?);
let ts = Timestamp::from(SystemTime::UNIX_EPOCH + 5.seconds() + 1.milliseconds());
assert_eq!(ts, Timestamp::from_milliseconds(5_001)?);
let ts = Timestamp::from(SystemTime::UNIX_EPOCH - 5.seconds() - 1.milliseconds());
assert_eq!(ts, Timestamp::from_milliseconds(-5_001)?);
Ok(())
}
}
@@ -0,0 +1,86 @@
use core::cmp::Ordering;
use core::ops::Sub;
use crate::{SignedDuration, Timestamp, UtcDateTime};
impl Sub<UtcDateTime> for Timestamp {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: UtcDateTime) -> Self::Output {
SignedDuration::new(
self.as_seconds() - rhs.unix_timestamp(),
self.nanosecond().cast_signed() - rhs.nanosecond().cast_signed(),
)
}
}
impl Sub<Timestamp> for UtcDateTime {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: Timestamp) -> Self::Output {
SignedDuration::new(
self.unix_timestamp() - rhs.as_seconds(),
self.nanosecond().cast_signed() - rhs.nanosecond().cast_signed(),
)
}
}
impl PartialEq<UtcDateTime> for Timestamp {
#[expect(clippy::suspicious_operation_groupings, reason = "false positive")]
#[inline]
fn eq(&self, other: &UtcDateTime) -> bool {
self.as_seconds() == other.unix_timestamp() && self.nanosecond() == other.nanosecond()
}
}
impl PartialEq<Timestamp> for UtcDateTime {
#[inline]
fn eq(&self, other: &Timestamp) -> bool {
other == self
}
}
impl PartialOrd<UtcDateTime> for Timestamp {
#[inline]
fn partial_cmp(&self, other: &UtcDateTime) -> Option<Ordering> {
(self.as_seconds(), self.nanosecond())
.partial_cmp(&(other.unix_timestamp(), other.nanosecond()))
}
}
impl PartialOrd<Timestamp> for UtcDateTime {
#[inline]
fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl From<UtcDateTime> for Timestamp {
#[inline]
fn from(datetime: UtcDateTime) -> Self {
// Safety: The valid range of `Timestamp` and `UtcDateTime` are the same. Nanoseconds also
// have the same range.
unsafe {
Self::from_seconds(datetime.unix_timestamp())
.unwrap_unchecked()
.replace_nanosecond(datetime.nanosecond())
.unwrap_unchecked()
}
}
}
impl From<Timestamp> for UtcDateTime {
#[inline]
fn from(timestamp: Timestamp) -> Self {
// Safety: The valid range of `Timestamp` and `UtcDateTime` are the same. Nanoseconds also
// have the same range.
unsafe {
Self::from_unix_timestamp(timestamp.as_seconds())
.unwrap_unchecked()
.replace_nanosecond(timestamp.nanosecond())
.unwrap_unchecked()
}
}
}
@@ -0,0 +1,88 @@
use core::cmp::Ordering;
use core::ops::Sub;
use std::time::SystemTime;
use crate::{SignedDuration, UtcDateTime};
impl Sub<SystemTime> for UtcDateTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn sub(self, rhs: SystemTime) -> Self::Output {
self - Self::from(rhs)
}
}
impl Sub<UtcDateTime> for SystemTime {
type Output = SignedDuration;
/// # Panics
///
/// This may panic if an overflow occurs.
#[inline]
#[track_caller]
fn sub(self, rhs: UtcDateTime) -> Self::Output {
UtcDateTime::from(self) - rhs
}
}
impl PartialEq<SystemTime> for UtcDateTime {
#[inline]
fn eq(&self, rhs: &SystemTime) -> bool {
self == &Self::from(*rhs)
}
}
impl PartialEq<UtcDateTime> for SystemTime {
#[inline]
fn eq(&self, rhs: &UtcDateTime) -> bool {
&UtcDateTime::from(*self) == rhs
}
}
impl PartialOrd<SystemTime> for UtcDateTime {
#[inline]
fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
self.partial_cmp(&Self::from(*other))
}
}
impl PartialOrd<UtcDateTime> for SystemTime {
#[inline]
fn partial_cmp(&self, other: &UtcDateTime) -> Option<Ordering> {
UtcDateTime::from(*self).partial_cmp(other)
}
}
impl From<SystemTime> for UtcDateTime {
#[inline]
fn from(system_time: SystemTime) -> Self {
match system_time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => Self::UNIX_EPOCH + duration,
Err(err) => Self::UNIX_EPOCH - err.duration(),
}
}
}
impl From<UtcDateTime> for SystemTime {
/// # Panics
///
/// This may panic if the resulting `SystemTime` cannot be represented.
#[inline]
fn from(datetime: UtcDateTime) -> Self {
let duration = datetime - UtcDateTime::UNIX_EPOCH;
if duration.is_zero() {
Self::UNIX_EPOCH
} else if duration.is_positive() {
Self::UNIX_EPOCH + duration.unsigned_abs()
} else {
debug_assert!(duration.is_negative());
Self::UNIX_EPOCH - duration.unsigned_abs()
}
}
}
+218
View File
@@ -0,0 +1,218 @@
//! An iterator over a range of [`Date`]s, yielding each day from start to end inclusive.
use core::iter::FusedIterator;
use crate::Date;
/// An iterator over a range of [`Date`]s, yielding each day from start to end inclusive.
///
/// This struct is created by [`Date::iter_to`] or [`DateIter::new`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DateIter {
front: Date,
back: Option<Date>,
}
impl DateIter {
/// Create a new `DateIter` from `start` to `end` inclusive.
///
/// If `start > end`, the iterator will be empty.
///
/// ```rust
/// # use time::iter::DateIter;
/// # use time_macros::date;
/// let mut iter = DateIter::new(date!(2019-01-01), date!(2019-01-03));
/// assert_eq!(iter.next(), Some(date!(2019-01-01)));
/// assert_eq!(iter.next(), Some(date!(2019-01-02)));
/// assert_eq!(iter.next(), Some(date!(2019-01-03)));
/// assert_eq!(iter.next(), None);
/// ```
#[inline]
pub const fn new(start: Date, end: Date) -> Self {
Self {
front: start,
back: if start.as_i32() <= end.as_i32() {
Some(end)
} else {
None
},
}
}
/// Returns `true` if the iterator is empty, `false` otherwise.
#[inline]
const fn is_exhausted(&self) -> bool {
self.back.is_none()
}
/// Make the iterator exhausted.
#[inline]
const fn make_exhausted(&mut self) {
self.back = None;
}
/// Return the number of days from `front` to `back`. Uses ordinal arithmetic as a fast path
/// when both dates fall in the same year, avoiding the more expensive Julian day computation.
#[inline]
const fn days_between(front: Date, back: Date) -> i32 {
if front.year() == back.year() {
back.ordinal() as i32 - front.ordinal() as i32
} else {
back.to_julian_day() - front.to_julian_day()
}
}
}
impl Iterator for DateIter {
type Item = Date;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.front;
let back = self.back?;
if current < back {
// Safety: `current < back`, so `current` has a successor.
self.front = unsafe { current.next_day().unwrap_unchecked() };
} else {
self.make_exhausted();
}
Some(current)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
#[inline]
fn count(self) -> usize {
self.len()
}
#[inline]
fn last(self) -> Option<Self::Item> {
self.back
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let back = self.back?;
let front = self.front;
let same_year = front.year() == back.year();
let remaining = if same_year {
(back.ordinal() - front.ordinal()) as usize
} else {
(back.to_julian_day() - front.to_julian_day()) as usize
};
if n > remaining {
self.make_exhausted();
return None;
}
// Fast path for when the result is in the same year, avoiding the more expensive Julian day
// computation.
let result = if same_year
|| front.ordinal() as usize <= if front.is_in_leap_year() { 366 } else { 365 } - n
{
// Safety: We know that we're staying in the same year and that the resulting ordinal is
// valid.
unsafe { front.add_days_unchecked(n as i32) }
} else {
// Safety: `n <= remaining = back_jd - front_jd`, so `front_jd + n <= back_jd`.
unsafe { Date::from_julian_day_unchecked(front.to_julian_day() + n as i32) }
};
if n == remaining {
self.make_exhausted();
} else {
// Safety: `result < back`, so `result` has a successor.
self.front = unsafe { result.next_day().unwrap_unchecked() };
}
Some(result)
}
#[inline]
fn max(self) -> Option<Self::Item> {
self.back
}
#[inline]
fn min(self) -> Option<Self::Item> {
(!self.is_exhausted()).then_some(self.front)
}
#[inline]
fn is_sorted(self) -> bool {
true
}
}
impl DoubleEndedIterator for DateIter {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let back = self.back?;
if self.front < back {
// Safety: `front < back`, so `back` has a predecessor.
self.back = Some(unsafe { back.previous_day().unwrap_unchecked() });
} else {
self.make_exhausted();
}
Some(back)
}
#[inline]
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
let back = self.back?;
let front = self.front;
let same_year = front.year() == back.year();
let remaining = if same_year {
(back.ordinal() - front.ordinal()) as usize
} else {
(back.to_julian_day() - front.to_julian_day()) as usize
};
if n > remaining {
self.make_exhausted();
return None;
}
let result = if same_year || back.ordinal() as usize > n {
// Safety: `back.ordinal() > n`, so subtracting stays within `back`'s year.
unsafe { back.add_days_unchecked(-(n as i32)) }
} else {
// Safety: `n <= remaining = back_jd - front_jd`, so `back_jd - n >= front_jd`.
unsafe { Date::from_julian_day_unchecked(back.to_julian_day() - n as i32) }
};
if n == remaining {
self.make_exhausted();
} else {
// Safety: `result > front`, so `result` has a predecessor.
self.back = Some(unsafe { result.previous_day().unwrap_unchecked() });
}
Some(result)
}
}
impl ExactSizeIterator for DateIter {
#[inline]
fn len(&self) -> usize {
let Some(back) = self.back else {
return 0;
};
Self::days_between(self.front, back) as usize + 1
}
}
impl FusedIterator for DateIter {}
+15
View File
@@ -0,0 +1,15 @@
//! Iterator types.
mod date_iter;
mod month_iter;
mod weekday_iter;
pub use self::date_iter::DateIter;
pub use self::month_iter::MonthIter;
pub use self::weekday_iter::WeekdayIter;
/// An iterator that yields the elements of an underlying iterator in reverse order.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rev<I> {
iter: I,
}
+177
View File
@@ -0,0 +1,177 @@
//! An infinite iterator over [`Month`]s.
use core::iter::FusedIterator;
use super::Rev;
use crate::Month::{self, *};
pub(super) const ALL_MONTHS: [Month; 12] = [
January, February, March, April, May, June, July, August, September, October, November,
December,
];
/// An infinite iterator over [`Month`]s.
///
/// This struct is created by [`Month::iter_from`] or [`MonthIter::new`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MonthIter {
current: Month,
}
impl MonthIter {
/// Create a new `MonthIter` starting at `start`.
///
/// ```rust
/// # use time::{Month, iter::MonthIter};
/// let mut iter = MonthIter::new(Month::January);
/// assert_eq!(iter.next(), Some(Month::January));
/// assert_eq!(iter.next(), Some(Month::February));
/// assert_eq!(iter.next(), Some(Month::March));
/// ```
#[inline]
pub const fn new(start: Month) -> Self {
Self { current: start }
}
/// Make the iterator go in reverse order.
///
/// ```rust
/// # use time::{Month, iter::MonthIter};
/// let mut iter = MonthIter::new(Month::January).rev();
/// assert_eq!(iter.next(), Some(Month::January));
/// assert_eq!(iter.next(), Some(Month::December));
/// assert_eq!(iter.next(), Some(Month::November));
/// ```
#[inline]
pub const fn rev(self) -> Rev<Self> {
Rev { iter: self }
}
}
impl Iterator for MonthIter {
type Item = Month;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.current;
self.current = current.next();
Some(current)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let result = self.current.nth_next((n % 12) as u8);
self.current = result.next();
Some(result)
}
fn count(self) -> usize {
panic!("`MonthIter` is infinite and cannot be counted")
}
fn last(self) -> Option<Self::Item> {
panic!("`MonthIter` is infinite and has no last element")
}
#[inline]
fn all<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_MONTHS.into_iter().all(f)
}
#[inline]
fn any<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_MONTHS.into_iter().any(f)
}
#[inline]
fn max(self) -> Option<Self::Item> {
Some(December)
}
#[inline]
fn min(self) -> Option<Self::Item> {
Some(January)
}
#[inline]
fn is_sorted(self) -> bool {
// The iterator will always have December followed by January, so it is not sorted.
false
}
}
impl Iterator for Rev<MonthIter> {
type Item = Month;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.iter.current;
self.iter.current = current.previous();
Some(current)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let result = self.iter.current.nth_prev((n % 12) as u8);
self.iter.current = result.previous();
Some(result)
}
fn count(self) -> usize {
panic!("`Rev<MonthIter>` is infinite and cannot be counted")
}
fn last(self) -> Option<Self::Item> {
panic!("`Rev<MonthIter>` is infinite and has no last element")
}
#[inline]
fn all<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_MONTHS.into_iter().all(f)
}
#[inline]
fn any<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_MONTHS.into_iter().any(f)
}
#[inline]
fn max(self) -> Option<Self::Item> {
Some(December)
}
#[inline]
fn min(self) -> Option<Self::Item> {
Some(January)
}
#[inline]
fn is_sorted(self) -> bool {
false
}
}
impl FusedIterator for MonthIter {}
impl FusedIterator for Rev<MonthIter> {}
+145
View File
@@ -0,0 +1,145 @@
//! An infinite iterator over [`Weekday`]s.
use core::iter::FusedIterator;
use super::Rev;
use crate::Weekday::{self, *};
const ALL_WEEKDAYS: [Weekday; 7] = [
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday,
];
/// An infinite iterator over [`Weekday`]s.
///
/// This struct is created by [`Weekday::iter_from`] or [`WeekdayIter::new`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WeekdayIter {
current: Weekday,
}
impl WeekdayIter {
/// Create a new `WeekdayIter` starting at `start`.
///
/// ```rust
/// # use time::{Weekday, iter::WeekdayIter};
/// let mut iter = WeekdayIter::new(Weekday::Monday);
/// assert_eq!(iter.next(), Some(Weekday::Monday));
/// assert_eq!(iter.next(), Some(Weekday::Tuesday));
/// assert_eq!(iter.next(), Some(Weekday::Wednesday));
/// ```
#[inline]
pub const fn new(start: Weekday) -> Self {
Self { current: start }
}
/// Make the iterator go in reverse order.
///
/// ```rust
/// # use time::{Weekday, iter::WeekdayIter};
/// let mut iter = WeekdayIter::new(Weekday::Monday).rev();
/// assert_eq!(iter.next(), Some(Weekday::Monday));
/// assert_eq!(iter.next(), Some(Weekday::Sunday));
/// assert_eq!(iter.next(), Some(Weekday::Saturday));
/// ```
#[inline]
pub const fn rev(self) -> Rev<Self> {
Rev { iter: self }
}
}
impl Iterator for WeekdayIter {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.current;
self.current = current.next();
Some(current)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let result = self.current.nth_next((n % 7) as u8);
self.current = result.next();
Some(result)
}
fn count(self) -> usize {
panic!("`WeekdayIter` is infinite and cannot be counted")
}
fn last(self) -> Option<Self::Item> {
panic!("`WeekdayIter` is infinite and has no last element")
}
#[inline]
fn all<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_WEEKDAYS.into_iter().all(f)
}
#[inline]
fn any<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_WEEKDAYS.into_iter().any(f)
}
}
impl Iterator for Rev<WeekdayIter> {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.iter.current;
self.iter.current = current.previous();
Some(current)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let result = self.iter.current.nth_prev((n % 7) as u8);
self.iter.current = result.previous();
Some(result)
}
fn count(self) -> usize {
panic!("`Rev<WeekdayIter>` is infinite and cannot be counted")
}
fn last(self) -> Option<Self::Item> {
panic!("`Rev<WeekdayIter>` is infinite and has no last element")
}
#[inline]
fn all<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_WEEKDAYS.into_iter().all(f)
}
#[inline]
fn any<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
ALL_WEEKDAYS.into_iter().any(f)
}
}
impl FusedIterator for WeekdayIter {}
impl FusedIterator for Rev<WeekdayIter> {}
+178
View File
@@ -0,0 +1,178 @@
//! # Feature flags
//!
//! This crate exposes a number of features. These can be enabled or disabled as shown
//! [in Cargo's documentation](https://doc.rust-lang.org/cargo/reference/features.html). Features
//! are _disabled_ by default unless otherwise noted.
//!
//! Reliance on a given feature is always indicated alongside the item definition.
//!
//! - `std` (_enabled by default, implicitly enables `alloc`_)
//!
//! This enables a number of features that depend on the standard library.
//!
//! - `alloc` (_enabled by default via `std`_)
//!
//! Enables a number of features that require the ability to dynamically allocate memory.
//!
//! - `macros`
//!
//! Enables macros that provide compile-time verification of values and intuitive syntax.
//!
//! - `formatting` (_implicitly enables `std`_)
//!
//! Enables formatting of most structs.
//!
//! - `parsing`
//!
//! Enables parsing of most structs.
//!
//! - `local-offset` (_implicitly enables `std`_)
//!
//! This feature enables a number of methods that allow obtaining the system's UTC offset.
//!
//! - `large-dates`
//!
//! By default, only years within the ±9999 range (inclusive) are supported. If you need support
//! for years outside this range, consider enabling this feature; the supported range will be
//! increased to ±999,999.
//!
//! Note that enabling this feature has some costs, as it means forgoing some optimizations.
//! Ambiguities may be introduced when parsing that would not otherwise exist.
//!
//! - `serde`
//!
//! Enables [`serde`](https://docs.rs/serde) support for all types.
//!
//! - `serde-human-readable` (_implicitly enables `serde`, `formatting`, and `parsing`_)
//!
//! Allows `serde` representations to use a human-readable format. This is determined by the
//! serializer, not the user. If this feature is not enabled or if the serializer requests a
//! non-human-readable format, a format optimized for binary representation will be used.
//!
//! Libraries should never enable this feature, as the decision of what format to use should be up
//! to the user.
//!
//! - `rand` (_implicitly enables `rand08`, `rand09`, and `rand010`_)
//!
//! Previously, this would enable support for `rand` 0.8. Since the release of `rand` 0.9, the
//! feature has been split into `rand08` and `rand09` to allow support for both versions. `rand`
//! 0.10 has since been added. For backwards compatibility and simplicity, this feature enables
//! support for _all_ series.
//!
//! It is strongly recommended to enable the version you need directly, as enabling `rand` will
//! needlessly pull in all versions.
//!
//! - `rand08`, `rand09`, `rand010`
//!
//! Enables [`rand` 0.8](https://docs.rs/rand/0.8), [`rand` 0.9](https://docs.rs/rand/0.9), and
//! [`rand` 0.10](https://docs.rs/rand/0.10) support for all types, respectively.
//!
//! - `quickcheck` (_implicitly enables `alloc`_)
//!
//! Enables [quickcheck](https://docs.rs/quickcheck) support for all types.
//!
//! - `wasm-bindgen`
//!
//! Enables [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) support for converting
//! [JavaScript dates](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Date.html), as
//! well as obtaining the UTC offset from JavaScript.
#![doc(html_playground_url = "https://play.rust-lang.org")]
#![cfg_attr(docsrs, feature(doc_cfg, doc_notable_trait))]
#![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))))]
#[allow(unused_extern_crates)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
/// Deprecated module for units of time.
#[deprecated(note = "import from `time::unit` instead")]
pub mod convert {
pub use time_core::unit::*;
}
mod date;
pub mod error;
pub mod ext;
#[cfg(any(feature = "formatting", feature = "parsing"))]
pub mod format_description;
#[cfg(feature = "formatting")]
pub mod formatting;
mod hint;
#[cfg(feature = "std")]
mod instant;
mod internal_macros;
mod interop;
pub mod iter;
#[cfg(feature = "macros")]
pub mod macros;
mod month;
mod num_fmt;
mod offset_date_time;
#[cfg(feature = "parsing")]
pub mod parsing;
mod plain_date_time;
#[cfg(feature = "quickcheck")]
mod quickcheck;
#[cfg(feature = "rand010")]
mod rand010;
#[cfg(feature = "rand08")]
mod rand08;
#[cfg(feature = "rand09")]
mod rand09;
#[cfg(feature = "serde")]
pub mod serde;
mod signed_duration;
mod sys;
#[cfg(test)]
mod tests;
mod time;
mod timestamp;
mod utc_date_time;
mod utc_offset;
pub mod util;
mod weekday;
pub use time_core::unit;
pub use crate::date::Date;
pub use crate::error::Error;
#[doc(hidden)]
#[cfg(feature = "std")]
#[expect(deprecated)]
pub use crate::instant::Instant;
pub use crate::month::Month;
pub use crate::offset_date_time::OffsetDateTime;
pub use crate::plain_date_time::PlainDateTime;
pub use crate::signed_duration::SignedDuration;
pub use crate::time::Time;
pub use crate::timestamp::Timestamp;
pub use crate::utc_date_time::UtcDateTime;
pub use crate::utc_offset::UtcOffset;
pub use crate::weekday::Weekday;
/// An alias for [`std::result::Result`] with a generic error from the time crate.
pub type Result<T> = core::result::Result<T, Error>;
/// A [`PlainDateTime`] under its original name.
///
/// This type is not currently deprecated, but it likely will be in the future. Use
/// [`PlainDateTime`] when writing new code. In the next breaking release, this alias will be
/// removed.
pub type PrimitiveDateTime = PlainDateTime;
/// A [`SignedDuration`] under its original name.
///
/// This type is not currently deprecated, but it likely will be in the future. Use
/// [`SignedDuration`] when writing new code. In the next breaking release, this alias will be
/// removed.
pub type Duration = SignedDuration;
/// A private type used to restrict the ability to call methods that are not intended to be called
/// by downstream users.
#[cfg(any(feature = "formatting", feature = "parsing"))]
#[derive(Debug)]
struct PrivateMethod;
+168
View File
@@ -0,0 +1,168 @@
//! Macros to construct statically known values.
/// Construct a [`Date`](crate::Date) with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// Three formats are supported: year-week-weekday, year-ordinal, and year-month-day.
///
/// ```rust
/// # use time::{Date, Weekday::*, Month, macros::date};
/// assert_eq!(
/// date!(2020-W01-3),
/// Date::from_iso_week_date(2020, 1, Wednesday)?
/// );
/// assert_eq!(date!(2020-001), Date::from_ordinal_date(2020, 1)?);
/// assert_eq!(
/// date!(2020-01-01),
/// Date::from_calendar_date(2020, Month::January, 1)?
/// );
/// # Ok::<_, time::Error>(())
/// ```
pub use time_macros::date;
/// Construct a [`PlainDateTime`] or [`OffsetDateTime`] with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// The syntax accepted by this macro is the same as [`date!`] and [`time!`], with an optional
/// [`offset!`], all space-separated. If an [`offset!`] is provided, the resulting value will
/// be an [`OffsetDateTime`]; otherwise it will be a [`PlainDateTime`].
///
/// [`OffsetDateTime`]: crate::OffsetDateTime
/// [`PlainDateTime`]: crate::PlainDateTime
///
/// ```rust
/// # use time::{Date, Month, macros::datetime, UtcOffset};
/// assert_eq!(
/// datetime!(2020-01-01 0:00),
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
/// );
/// assert_eq!(
/// datetime!(2020-01-01 0:00 UTC),
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().assume_utc()
/// );
/// assert_eq!(
/// datetime!(2020-01-01 0:00 -1),
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
/// .assume_offset(UtcOffset::from_hms(-1, 0, 0)?)
/// );
/// # Ok::<_, time::Error>(())
/// ```
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`](alloc::vec::Vec), such that it can be used in `#![no_alloc]` situations. For
/// readability, you can use [`StaticFormatDescription`] as the type.
///
/// [`StaticFormatDescription`]: crate::format_description::StaticFormatDescription
///
/// The resulting expression can be used in `const` or `static` declarations, and implements
/// the sealed traits required for both formatting and parsing.
#[cfg_attr(feature = "alloc", doc = "```rust")]
#[cfg_attr(not(feature = "alloc"), doc = "```rust,ignore")]
/// # use time::{format_description, macros::format_description};
/// assert_eq!(
/// format_description!("[hour]:[minute]:[second]"),
/// format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")?
/// );
/// # Ok::<_, time::Error>(())
/// ```
///
/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
///
/// [`format_description::parse()`]: crate::format_description::parse()
#[cfg(any(feature = "formatting", feature = "parsing"))]
pub use time_macros::format_description;
/// Construct a [`UtcOffset`](crate::UtcOffset) with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// A sign and the hour must be provided; minutes and seconds default to zero. `UTC` (both
/// uppercase and lowercase) is also allowed.
///
/// ```rust
/// # use time::{UtcOffset, macros::offset};
/// assert_eq!(offset!(UTC), UtcOffset::from_hms(0, 0, 0)?);
/// assert_eq!(offset!(utc), UtcOffset::from_hms(0, 0, 0)?);
/// assert_eq!(offset!(+0), UtcOffset::from_hms(0, 0, 0)?);
/// assert_eq!(offset!(+1), UtcOffset::from_hms(1, 0, 0)?);
/// assert_eq!(offset!(-1), UtcOffset::from_hms(-1, 0, 0)?);
/// assert_eq!(offset!(+1:30), UtcOffset::from_hms(1, 30, 0)?);
/// assert_eq!(offset!(-1:30), UtcOffset::from_hms(-1, -30, 0)?);
/// assert_eq!(offset!(+1:30:59), UtcOffset::from_hms(1, 30, 59)?);
/// assert_eq!(offset!(-1:30:59), UtcOffset::from_hms(-1, -30, -59)?);
/// assert_eq!(offset!(+23:59:59), UtcOffset::from_hms(23, 59, 59)?);
/// assert_eq!(offset!(-23:59:59), UtcOffset::from_hms(-23, -59, -59)?);
/// # Ok::<_, time::Error>(())
/// ```
pub use time_macros::offset;
/// Construct a [`Time`](crate::Time) with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// Hours and minutes must be provided, while seconds defaults to zero. AM/PM is allowed
/// (either uppercase or lowercase). Any number of subsecond digits may be provided (though any
/// past nine will be discarded).
///
/// All components are validated at compile-time. An error will be raised if any value is
/// invalid.
///
/// ```rust
/// # use time::{Time, macros::time};
/// assert_eq!(time!(0:00), Time::from_hms(0, 0, 0)?);
/// assert_eq!(time!(1:02:03), Time::from_hms(1, 2, 3)?);
/// assert_eq!(
/// time!(1:02:03.004_005_006),
/// Time::from_hms_nano(1, 2, 3, 4_005_006)?
/// );
/// assert_eq!(time!(12:00 am), Time::from_hms(0, 0, 0)?);
/// assert_eq!(time!(1:02:03 am), Time::from_hms(1, 2, 3)?);
/// assert_eq!(
/// time!(1:02:03.004_005_006 am),
/// Time::from_hms_nano(1, 2, 3, 4_005_006)?
/// );
/// assert_eq!(time!(12 pm), Time::from_hms(12, 0, 0)?);
/// assert_eq!(time!(12:00 pm), Time::from_hms(12, 0, 0)?);
/// assert_eq!(time!(1:02:03 pm), Time::from_hms(13, 2, 3)?);
/// assert_eq!(
/// time!(1:02:03.004_005_006 pm),
/// Time::from_hms_nano(13, 2, 3, 4_005_006)?
/// );
/// # Ok::<_, time::Error>(())
/// ```
pub use time_macros::time;
/// Construct a [`Timestamp`](crate::Timestamp) with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// The syntax accepted by this macro is a timestamp (as an integer or float) _or_ the same as
/// [`utc_datetime!`].
///
/// ```rust
/// # use time::Timestamp;
/// # use time::macros::timestamp;
/// assert_eq!(timestamp!(0), Timestamp::UNIX_EPOCH);
/// assert_eq!(timestamp!(1970-01-01 0:00), Timestamp::UNIX_EPOCH);
/// assert_eq!(timestamp!(1_234_567_890), Timestamp::new(1_234_567_890, 0)?);
/// # Ok::<_, time::Error>(())
/// ```
pub use time_macros::timestamp;
/// Construct a [`UtcDateTime`] with a statically known value.
///
/// The resulting expression can be used in `const` or `static` declarations.
///
/// The syntax accepted by this macro is the same as a space-separated [`date!`] and [`time!`].
///
/// [`UtcDateTime`]: crate::UtcDateTime
///
/// ```rust
/// # use time::{Date, Month, macros::utc_datetime};
/// assert_eq!(
/// utc_datetime!(2020-01-01 0:00),
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().as_utc()
/// );
/// # Ok::<_, time::Error>(())
/// ```
pub use time_macros::utc_datetime;
+292
View File
@@ -0,0 +1,292 @@
//! The `Month` enum and its associated `impl`s.
use core::fmt;
use core::num::NonZero;
use core::str::FromStr;
use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay};
use self::Month::*;
use crate::iter::MonthIter;
use crate::{error, util};
/// Months of the year.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Month {
#[expect(missing_docs)]
January = 1,
#[expect(missing_docs)]
February = 2,
#[expect(missing_docs)]
March = 3,
#[expect(missing_docs)]
April = 4,
#[expect(missing_docs)]
May = 5,
#[expect(missing_docs)]
June = 6,
#[expect(missing_docs)]
July = 7,
#[expect(missing_docs)]
August = 8,
#[expect(missing_docs)]
September = 9,
#[expect(missing_docs)]
October = 10,
#[expect(missing_docs)]
November = 11,
#[expect(missing_docs)]
December = 12,
}
impl Month {
/// Create a `Month` from its numerical value.
#[inline]
pub(crate) const fn from_number(n: NonZero<u8>) -> Result<Self, error::ComponentRange> {
match n.get() {
1 => Ok(January),
2 => Ok(February),
3 => Ok(March),
4 => Ok(April),
5 => Ok(May),
6 => Ok(June),
7 => Ok(July),
8 => Ok(August),
9 => Ok(September),
10 => Ok(October),
11 => Ok(November),
12 => Ok(December),
_ => Err(error::ComponentRange::unconditional("month")),
}
}
/// Get the number of days in the month of a given year.
///
/// ```rust
/// # use time::Month;
/// assert_eq!(Month::February.length(2020), 29);
/// ```
#[inline]
pub const fn length(self, year: i32) -> u8 {
util::days_in_month(self, year)
}
/// Get the previous month.
///
/// ```rust
/// # use time::Month;
/// assert_eq!(Month::January.previous(), Month::December);
/// ```
#[inline]
pub const fn previous(self) -> Self {
match self {
January => December,
February => January,
March => February,
April => March,
May => April,
June => May,
July => June,
August => July,
September => August,
October => September,
November => October,
December => November,
}
}
/// Get the next month.
///
/// ```rust
/// # use time::Month;
/// assert_eq!(Month::January.next(), Month::February);
/// ```
#[inline]
pub const fn next(self) -> Self {
match self {
January => February,
February => March,
March => April,
April => May,
May => June,
June => July,
July => August,
August => September,
September => October,
October => November,
November => December,
December => January,
}
}
/// Get n-th next month.
///
/// ```rust
/// # use time::Month;
/// assert_eq!(Month::January.nth_next(4), Month::May);
/// assert_eq!(Month::July.nth_next(9), Month::April);
/// ```
#[inline]
pub const fn nth_next(self, n: u8) -> Self {
match (self as u8 - 1 + n % 12) % 12 {
0 => January,
1 => February,
2 => March,
3 => April,
4 => May,
5 => June,
6 => July,
7 => August,
8 => September,
9 => October,
10 => November,
val => {
debug_assert!(val == 11);
December
}
}
}
/// Get n-th previous month.
///
/// ```rust
/// # use time::Month;
/// assert_eq!(Month::January.nth_prev(4), Month::September);
/// assert_eq!(Month::July.nth_prev(9), Month::October);
/// ```
#[inline]
pub const fn nth_prev(self, n: u8) -> Self {
match self as i8 - 1 - (n % 12).cast_signed() {
1 | -11 => February,
2 | -10 => March,
3 | -9 => April,
4 | -8 => May,
5 | -7 => June,
6 | -6 => July,
7 | -5 => August,
8 | -4 => September,
9 | -3 => October,
10 | -2 => November,
11 | -1 => December,
val => {
debug_assert!(val == 0);
January
}
}
}
/// Create an infinite iterator starting at this month.
///
/// ```rust
/// # use time::Month;
/// let mut iter = Month::iter_from(Month::January);
/// assert_eq!(iter.next(), Some(Month::January));
/// assert_eq!(iter.next(), Some(Month::February));
/// assert_eq!(iter.next(), Some(Month::March));
/// assert_eq!(iter.next(), Some(Month::April));
/// assert_eq!(iter.next(), Some(Month::May));
/// assert_eq!(iter.next(), Some(Month::June));
/// assert_eq!(iter.next(), Some(Month::July));
/// assert_eq!(iter.next(), Some(Month::August));
/// assert_eq!(iter.next(), Some(Month::September));
/// assert_eq!(iter.next(), Some(Month::October));
/// assert_eq!(iter.next(), Some(Month::November));
/// assert_eq!(iter.next(), Some(Month::December));
/// assert_eq!(iter.next(), Some(Month::January));
/// // … continuing forever
/// ```
#[inline]
pub const fn iter_from(start: Self) -> MonthIter {
MonthIter::new(start)
}
}
impl SmartDisplay for Month {
type Metadata = ();
#[inline]
fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
match self {
January => Metadata::new(7, self, ()),
February => Metadata::new(8, self, ()),
March => Metadata::new(5, self, ()),
April => Metadata::new(5, self, ()),
May => Metadata::new(3, self, ()),
June => Metadata::new(4, self, ()),
July => Metadata::new(4, self, ()),
August => Metadata::new(6, self, ()),
September => Metadata::new(9, self, ()),
October => Metadata::new(7, self, ()),
November => Metadata::new(8, self, ()),
December => Metadata::new(8, self, ()),
}
}
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(match self {
January => "January",
February => "February",
March => "March",
April => "April",
May => "May",
June => "June",
July => "July",
August => "August",
September => "September",
October => "October",
November => "November",
December => "December",
})
}
}
impl fmt::Display for Month {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self, f)
}
}
impl FromStr for Month {
type Err = error::InvalidVariant;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"January" => Ok(January),
"February" => Ok(February),
"March" => Ok(March),
"April" => Ok(April),
"May" => Ok(May),
"June" => Ok(June),
"July" => Ok(July),
"August" => Ok(August),
"September" => Ok(September),
"October" => Ok(October),
"November" => Ok(November),
"December" => Ok(December),
_ => Err(error::InvalidVariant),
}
}
}
impl From<Month> for u8 {
#[inline]
fn from(month: Month) -> Self {
month as Self
}
}
impl TryFrom<u8> for Month {
type Error = error::ComponentRange;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match NonZero::new(value) {
Some(value) => Self::from_number(value),
None => Err(error::ComponentRange::unconditional("month")),
}
}
}
+630
View File
@@ -0,0 +1,630 @@
//! Formatting utilities for numbers.
//!
//! These functions are low-level, but are designed to be _extremely_ fast for their designed use
//! cases. They have strict requirements, and may not return the most ergonomic types to avoid
//! unnecessary allocations and copying.
use core::mem::MaybeUninit;
use core::ops::Deref;
use core::{ptr, slice};
#[cfg(feature = "formatting")]
use deranged::ru64;
use deranged::{ru8, ru16, ru32};
static SINGLE_DIGITS: [u8; 10] = *b"0123456789";
static ZERO_PADDED_PAIRS: [u8; 200] = *b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
#[cfg(feature = "formatting")]
static SPACE_PADDED_PAIRS: [u8; 200] = *b" 0 1 2 3 4 5 6 7 8 910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
/// A string type with a maximum length known at compile time, stored on the stack.
///
/// Note that while the _maximum_ length is known at compile time, the string may be shorter. This
/// information is stored inline.
#[derive(Clone, Copy)]
pub(crate) struct StackStr<const MAX_LEN: usize> {
buf: [MaybeUninit<u8>; MAX_LEN],
len: usize,
}
impl<const MAX_LEN: usize> StackStr<MAX_LEN> {
/// # Safety:
///
/// - `buf` must be initialized for at least `len` bytes.
/// - The first `len` bytes of `buf` must be valid UTF-8.
#[inline]
pub(crate) const unsafe fn new(buf: [MaybeUninit<u8>; MAX_LEN], len: usize) -> Self {
debug_assert!(len <= MAX_LEN);
Self { buf, len }
}
}
impl<const MAX_LEN: usize> Deref for StackStr<MAX_LEN> {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
// Safety: This type can only be constructed when the caller asserts that the buffer is
// valid UTF-8 for the first `len` bytes.
unsafe { str_from_raw_parts(self.buf.as_ptr().cast(), self.len) }
}
}
/// A string type with a maximum length known at compile time, stored on the stack.
///
/// Note that while the _maximum_ length is known at compile time, the string may be shorter. This
/// information is stored inline.
#[derive(Clone, Copy)]
pub(crate) struct StackTrailingStr<const MAX_LEN: usize> {
buf: [MaybeUninit<u8>; MAX_LEN],
start_index: usize,
}
impl<const MAX_LEN: usize> StackTrailingStr<MAX_LEN> {
/// # Safety:
///
/// - The last `MAX_LEN - start_index` bytes of `buf` must be initialized and valid UTF-8.
#[inline]
pub(crate) const unsafe fn new(buf: [MaybeUninit<u8>; MAX_LEN], start_index: usize) -> Self {
debug_assert!(start_index <= MAX_LEN);
Self { buf, start_index }
}
/// Return the length of `self` in bytes.
#[inline]
pub(crate) const fn len(&self) -> usize {
let len = MAX_LEN - self.start_index;
// Safety: `self.start_index` is an unsigned integer, so `len` cannot be larger than
// `MAX_LEN` with the arithmetic above.
unsafe { core::hint::assert_unchecked(len <= MAX_LEN) };
len
}
}
impl<const MAX_LEN: usize> Deref for StackTrailingStr<MAX_LEN> {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
// Safety: This type can only be constructed when the caller asserts that the buffer is
// valid UTF-8 for the last `len` bytes.
unsafe {
str_from_raw_parts(
self.buf.as_ptr().add(self.start_index).cast(),
MAX_LEN - self.start_index,
)
}
}
}
/// Write a two digit integer to `buf` at `offset` and `offset + 1`.
///
/// # Safety
///
/// `buf` must be at least `offset + 2` bytes long.
#[inline]
const unsafe fn write_two_digits(buf: &mut [MaybeUninit<u8>], offset: usize, value: ru8<0, 99>) {
// Safety: `buf` is at least `offset + 2` bytes long.
unsafe {
ptr::copy_nonoverlapping(
two_digits_zero_padded(value).as_ptr().cast(),
buf.as_mut_ptr().add(offset),
2,
);
}
}
/// Write a single digit integer to `buf` at `offset`.
///
/// # Safety
///
/// `buf` must be at least `offset` bytes long.
#[inline]
const unsafe fn write_one_digit(buf: &mut [MaybeUninit<u8>], offset: usize, value: ru8<0, 9>) {
// Safety: `buf` is at least `offset` bytes long.
unsafe {
ptr::copy_nonoverlapping(
single_digit(value).as_ptr().cast(),
buf.as_mut_ptr().add(offset),
1,
);
}
}
/// # Safety
///
/// - `ptr` must be non-null and point to `len` initialized bytes of UTF-8 data.
/// - `ptr` is valid for (and not mutated during) lifetime `'a`.
#[inline]
pub(crate) const unsafe fn str_from_raw_parts<'a>(ptr: *const u8, len: usize) -> &'a str {
// Safety: The caller must ensure that `ptr` is valid for `len` bytes and that the bytes are
// valid UTF-8. The caller must also ensure that the lifetime `'a` is valid for the returned
// string.
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) }
}
#[inline]
const fn div_100(n: ru16<0, 9_999>) -> [ru8<0, 99>; 2] {
const EXP: u32 = 19; // 19 is faster or equal to 12 even for 3 digits.
const SIG: u32 = (1 << EXP) / 100 + 1;
let n = n.get();
let high = (n as u32 * SIG) >> EXP; // value / 100
let low = n as u32 - high * 100;
// Safety: `high` is guaranteed to be less than 100 and `low` is guaranteed to be less than 100
// due to the arithmetic above.
unsafe {
[
ru8::new_unchecked(high as u8),
ru8::new_unchecked(low as u8),
]
}
}
/// Obtain a string containing a single ASCII digit representing `n`.
#[inline]
pub(crate) const fn single_digit(n: ru8<0, 9>) -> &'static str {
// Safety: We're staying within the bounds of the array. The array contains only ASCII
// characters, so it's valid UTF-8.
unsafe { str_from_raw_parts(SINGLE_DIGITS.as_ptr().add(n.get() as usize), 1) }
}
/// Obtain a string of one or two ASCII digits representing `n`. No leading zeros or spaces are
/// included.
#[inline]
pub(crate) const fn one_to_two_digits_no_padding(n: ru8<0, 99>) -> &'static str {
let n = n.get();
let is_single_digit = n < 10;
// Safety: We're staying within the bounds of the array. The array contains only ASCII
// characters, so it's valid UTF-8.
unsafe {
str_from_raw_parts(
ZERO_PADDED_PAIRS
.as_ptr()
.add((n as usize) * 2 + is_single_digit as usize),
2 - is_single_digit as usize,
)
}
}
/// Obtain a string of two ASCII digits representing `n`. This includes a leading zero if `n` is
/// less than 10.
#[inline]
pub(crate) const fn two_digits_zero_padded(n: ru8<0, 99>) -> &'static str {
// Safety: We're staying within the bounds of the array. The array contains only ASCII
// characters, so it's valid UTF-8.
unsafe { str_from_raw_parts(ZERO_PADDED_PAIRS.as_ptr().add((n.get() as usize) * 2), 2) }
}
/// Obtain a string of two ASCII digits representing `n`. This includes a leading space if `n` is
/// less than 10.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn two_digits_space_padded(n: ru8<0, 99>) -> &'static str {
// Safety: We're staying within the bounds of the array. The array contains only ASCII
// characters, so it's valid UTF-8.
unsafe { str_from_raw_parts(SPACE_PADDED_PAIRS.as_ptr().add((n.get() as usize) * 2), 2) }
}
/// Obtain two strings of ASCII digits representing `n`. The first string is most significant. No
/// leading zeros or spaces are included.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) fn one_to_three_digits_no_padding(n: ru16<0, 999>) -> [&'static str; 2] {
if let Some(n) = n.narrow::<0, 99>() {
crate::hint::cold_path();
["", one_to_two_digits_no_padding(n.into())]
} else {
three_digits_zero_padded(n)
}
}
/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
/// Leading zeros are included if the number has fewer than 3 digits.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn three_digits_zero_padded(n: ru16<0, 999>) -> [&'static str; 2] {
let [high, low] = div_100(n.expand());
[
// Safety: `high` is guaranteed to be less than 10 due to the range of the input.
single_digit(unsafe { high.narrow_unchecked() }),
two_digits_zero_padded(low),
]
}
/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
/// Leading spaces are included if the number has fewer than 3 digits.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn three_digits_space_padded(n: ru16<0, 999>) -> [&'static str; 2] {
let [high, low] = div_100(n.expand());
if let Some(high) = high.narrow::<1, 9>() {
[single_digit(high.expand()), two_digits_zero_padded(low)]
} else {
[" ", two_digits_space_padded(low)]
}
}
/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
/// No leading zeros or spaces are included.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) fn one_to_four_digits_no_padding(n: ru16<0, 9_999>) -> [&'static str; 2] {
if let Some(n) = n.narrow::<0, 999>() {
crate::hint::cold_path();
one_to_three_digits_no_padding(n)
} else {
four_digits_zero_padded(n)
}
}
/// Obtain two strings of two ASCII digits each representing `n`. The first string is the most
/// significant. Leading zeros are included if the number has fewer than 4 digits.
#[inline]
pub(crate) const fn four_digits_zero_padded(n: ru16<0, 9_999>) -> [&'static str; 2] {
let [high, low] = div_100(n);
[two_digits_zero_padded(high), two_digits_zero_padded(low)]
}
/// Obtain two strings of two ASCII digits each representing `n`. The first string is the most
/// significant. Leading spaces are included if the number has fewer than 4 digits.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn four_digits_space_padded(n: ru16<0, 9_999>) -> [&'static str; 2] {
let [high, low] = div_100(n);
if high.get() == 0 {
[" ", two_digits_space_padded(low)]
} else {
[two_digits_space_padded(high), two_digits_zero_padded(low)]
}
}
/// Obtain three strings which together represent `n`. The first string is the most significant.
/// Leading zeros are included if the number has fewer than 4 digits. The first string will be empty
/// if `n` is less than 10,000.
#[inline]
pub(crate) const fn four_to_six_digits(n: ru32<0, 999_999>) -> [&'static str; 3] {
let n = n.get();
let (first_two, remaining) = (n / 10_000, n % 10_000);
let size = 2 - (first_two < 10) as usize - (first_two == 0) as usize;
let offset = first_two as usize * 2 + 2 - size;
// Safety: `offset` is within the bounds of the array. The array contains only ASCII characters,
// so it's valid UTF-8.
let first_two = unsafe { str_from_raw_parts(ZERO_PADDED_PAIRS.as_ptr().add(offset), size) };
// Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
let [second_two, last_two] =
four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
[first_two, second_two, last_two]
}
/// Obtain three strings which together represent `n`. The first string is the most significant.
/// Leading zeros are included if the number has fewer than 5 digits. The first string will be empty
/// if `n` is less than 10,000.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn five_digits_zero_padded(n: ru32<0, 99_999>) -> [&'static str; 3] {
let n = n.get();
let (first_one, remaining) = (n / 10_000, n % 10_000);
// Safety: `first_one` is guaranteed to be less than 10 due to the division above.
let first_one = single_digit(unsafe { ru8::new_unchecked(first_one as u8) });
// Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
let [second_two, last_two] =
four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
[first_one, second_two, last_two]
}
/// Obtain three strings which together represent `n`. The first string is the most significant.
/// Leading zeroes are included if the number has fewer than 6 digits.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn six_digits_zero_padded(n: ru32<0, 999_999>) -> [&'static str; 3] {
let n = n.get();
let (first_two, remaining) = (n / 10_000, n % 10_000);
// Safety: `first_two` is guaranteed to be less than 100 due to the division above.
let first_two = two_digits_zero_padded(unsafe { ru8::new_unchecked(first_two as u8) });
// Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
let [second_two, last_two] =
four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
[first_two, second_two, last_two]
}
/// Obtain five strings which together represent `n`, which is a number of nanoseconds.
///
/// This value is intended to be used after a decimal point to represent a fractional second. The
/// first string will always contain exactly one digit; the remaining four will contain two digits
/// each.
#[inline]
pub(crate) const fn subsecond_from_nanos(n: ru32<0, 999_999_999>) -> [&'static str; 5] {
let n = n.get();
let (digits_1_thru_5, digits_6_thru_9) = (n / 10_000, n % 10_000);
let (digit_1, digits_2_thru_5) = (digits_1_thru_5 / 10_000, digits_1_thru_5 % 10_000);
// Safety: The type of `n` ensures that `n` is less than 1,000,000,000. Combined with the
// arithmetic above, this guarantees that all values are in the required ranges.
unsafe {
let digit_1 = single_digit(ru8::new_unchecked(digit_1 as u8));
let [digits_2_and_3, digits_4_and_5] =
four_digits_zero_padded(ru16::new_unchecked(digits_2_thru_5 as u16));
let [digits_6_and_7, digits_8_and_9] =
four_digits_zero_padded(ru16::new_unchecked(digits_6_thru_9 as u16));
[
digit_1,
digits_2_and_3,
digits_4_and_5,
digits_6_and_7,
digits_8_and_9,
]
}
}
/// Obtain a string of 1 to 9 ASCII digits representing `n`, which is a number of nanoseconds.
///
/// This value is intended to be used after a decimal point to represent a fractional second.
/// Trailing zeros are truncated, but at least one digit is always present.
#[inline]
pub(crate) const fn truncated_subsecond_from_nanos(n: ru32<0, 999_999_999>) -> StackStr<9> {
#[repr(C, align(8))]
#[derive(Clone, Copy)]
struct Digits {
_padding: MaybeUninit<[u8; 7]>,
digit_1: u8,
digits_2_thru_9: [u8; 8],
}
let [
digit_1,
digits_2_and_3,
digits_4_and_5,
digits_6_and_7,
digits_8_and_9,
] = subsecond_from_nanos(n);
// Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows the
// conversion to a `u64` to be zero cost, resulting in a nontrivial performance improvement.
let buf = Digits {
_padding: MaybeUninit::uninit(),
digit_1: digit_1.as_bytes()[0],
digits_2_thru_9: [
digits_2_and_3.as_bytes()[0],
digits_2_and_3.as_bytes()[1],
digits_4_and_5.as_bytes()[0],
digits_4_and_5.as_bytes()[1],
digits_6_and_7.as_bytes()[0],
digits_6_and_7.as_bytes()[1],
digits_8_and_9.as_bytes()[0],
digits_8_and_9.as_bytes()[1],
],
};
// By converting the bytes into a single integer, we can effectively perform an equality check
// against b'0' for all bytes at once. This is actually faster than using portable SIMD (even
// with `-Ctarget-cpu=native`).
let bitmask = u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
let digits_to_truncate = bitmask.leading_zeros() / 8;
let len = 9 - digits_to_truncate as usize;
// Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of bytes
// we wish to display (that is between 1 and 9 inclusive). `Digits` is `#[repr(C)]`, so the
// layout is guaranteed.
unsafe {
StackStr::new(
*(&raw const buf)
.byte_add(core::mem::offset_of!(Digits, digit_1))
.cast(),
len,
)
}
}
/// Format a `u64` into a string with no padding.
#[inline]
pub(crate) const fn u64_pad_none(value: u64) -> StackTrailingStr<20> {
let mut bytes = [MaybeUninit::uninit(); 20];
let mut offset = 20;
let mut remain = value;
while remain > 999 {
offset -= 4;
let quad = remain % 1_00_00;
remain /= 1_00_00;
// Safety: `quad` is guaranteed to be less than 10,000 due to the modulus above.
let [pair1, pair2] = div_100(unsafe { ru16::new_unchecked(quad as u16) });
// Safety: `buf` is at least `offset + 4` bytes long.
unsafe {
write_two_digits(&mut bytes, offset, pair1);
write_two_digits(&mut bytes, offset + 2, pair2);
}
}
if remain > 9 {
offset -= 2;
// Safety: `remain` is guaranteed to be less than 10,000 due to the loop above.
let [last, pair] = div_100(unsafe { ru16::new_unchecked(remain as u16) });
remain = last.get() as u64;
// Safety: `buf` is at least `offset + 2` bytes long.
unsafe { write_two_digits(&mut bytes, offset, pair) };
}
if remain != 0 || value == 0 {
offset -= 1;
let last = remain as u8 & 15;
// Safety: `offset` is known to be in bounds, and the value is known to be less than 10 due
// to the conditionals and bitwise AND above.
unsafe { write_one_digit(&mut bytes, offset, ru8::new_unchecked(last)) };
}
// Safety: All bytes starting at `offset` are initialized and valid UTF-8.
unsafe { StackTrailingStr::new(bytes, offset) }
}
/// Format a `u128` into a string with no padding.
#[inline]
#[cfg(feature = "formatting")]
pub(crate) const fn u128_pad_none(value: u128) -> StackTrailingStr<39> {
let mut bytes = [MaybeUninit::uninit(); 39];
// Take the 16 least-significant decimals.
let (quot_1e16, mod_1e16) = div_rem_1e16(value);
let (mut remain, mut offset) = if quot_1e16 == 0 {
(mod_1e16.get(), 39)
} else {
// Write digits at buf[23..39].
// Safety: `bytes` is 39 bytes long, so writing at offset 23 for 16 bytes is sound.
unsafe { enc_16lsd::<23>(&mut bytes, mod_1e16) };
// Take another 16 decimals.
let (quot2, mod2) = div_rem_1e16(quot_1e16);
if quot2 == 0 {
(mod2.get(), 23)
} else {
// Write digits at buf[7..23].
// Safety: `bytes` is 39 bytes long, so writing at offset 7 for 16 bytes is sound.
unsafe { enc_16lsd::<7>(&mut bytes, mod2) };
// Safety: `quot2`` has at most 7 decimals remaining after two 1e16 divisions.
(quot2 as u64, 7)
}
};
// Format per four digits from the lookup table.
while remain > 999 {
offset -= 4;
// pull two pairs
let quad = remain % 1_00_00;
remain /= 1_00_00;
// Safety: `quad` is guaranteed to be less than 10,000 due to the modulus above.
let [pair1, pair2] = div_100(unsafe { ru16::new_unchecked(quad as u16) });
// Safety: `buf` is at least `offset + 4` bytes long.
unsafe {
write_two_digits(&mut bytes, offset, pair1);
write_two_digits(&mut bytes, offset + 2, pair2);
}
}
// Format per two digits from the lookup table.
if remain > 9 {
offset -= 2;
// Safety: `remain` is guaranteed to be less than 10,000 due to the loop above.
let [last, pair] = div_100(unsafe { ru16::new_unchecked(remain as u16) });
remain = last.get() as u64;
// Safety: `buf` is at least `offset + 2` bytes long.
unsafe { write_two_digits(&mut bytes, offset, pair) };
}
// Format the last remaining digit, if any.
if remain != 0 || value == 0 {
offset -= 1;
// Either the compiler sees that remain < 10, or it prevents a boundary check up next.
let last = remain as u8 & 15;
// Safety: `offset` is known to be in bounds, and the value is known to be less than 10 due
// to the conditionals and bitwise AND above.
unsafe { write_one_digit(&mut bytes, offset, ru8::new_unchecked(last)) };
}
// Safety: All bytes starting at `offset` are initialized and valid UTF-8.
unsafe { StackTrailingStr::new(bytes, offset) }
}
/// Encodes the 16 least-significant decimals of n into `buf[OFFSET..OFFSET + 16]`.
///
/// # Safety
///
/// - `buf` must be at least `OFFSET + 16` bytes long.
#[cfg(feature = "formatting")]
const unsafe fn enc_16lsd<const OFFSET: usize>(
buf: &mut [MaybeUninit<u8>],
n: ru64<0, 9999_9999_9999_9999>,
) {
// Consume the least-significant decimals from a working copy.
let mut remain = n.get();
let mut quad_index = 3;
while quad_index >= 1 {
let quad = remain % 1_00_00;
remain /= 1_00_00;
// Safety: `quad` is guaranteed to be less than 10,000 due to the modulus above.
let [pair1, pair2] = div_100(unsafe { ru16::new_unchecked(quad as u16) });
// Safety: `buf` is at least `quad_index * 4 + OFFSET + 4` bytes long.
unsafe {
write_two_digits(buf, quad_index * 4 + OFFSET, pair1);
write_two_digits(buf, quad_index * 4 + OFFSET + 2, pair2);
}
quad_index -= 1;
}
// Safety: `remain` is guaranteed to be less than 10,000 due the range of `n` and the arithmetic
// in the loop above.
let [pair1, pair2] = div_100(unsafe { ru16::new_unchecked(remain as u16) });
// Safety: `buf` is at least `OFFSET + 4` bytes long.
unsafe {
write_two_digits(buf, OFFSET, pair1);
write_two_digits(buf, OFFSET + 2, pair2);
}
}
// Euclidean division plus remainder with constant 1e16 basically consumes 16
// decimals from n.
#[cfg(feature = "formatting")]
const fn div_rem_1e16(n: u128) -> (u128, ru64<0, 9999_9999_9999_9999>) {
const D: u128 = 1_0000_0000_0000_0000;
if n < D {
// Safety: We just checked that `n` is in range.
return (0, unsafe { ru64::new_unchecked(n as u64) });
}
const M_HIGH: u128 = 76_624_777_043_294_442_917_917_351_357_515_459_181;
const SH_POST: u8 = 51;
// n.widening_mul(M_HIGH).1 >> SH_POST
let quot = mulhi(n, M_HIGH) >> SH_POST;
let rem = n - quot * D;
// Safety: The arithmetic above ensures that `rem` is in range.
(quot, unsafe { ru64::new_unchecked(rem as u64) })
}
/// Multiply unsigned 128 bit integers, return upper 128 bits of the result
#[inline]
#[cfg(feature = "formatting")]
const fn mulhi(x: u128, y: u128) -> u128 {
let x_lo = x as u64;
let x_hi = (x >> 64) as u64;
let y_lo = y as u64;
let y_hi = (y >> 64) as u64;
// handle possibility of overflow
let carry = (x_lo as u128 * y_lo as u128) >> 64;
let m = x_lo as u128 * y_hi as u128 + carry;
let high1 = m >> 64;
let m_lo = m as u64;
let high2 = (x_hi as u128 * y_lo as u128 + m_lo as u128) >> 64;
x_hi as u128 * y_hi as u128 + high1 + high2
}
File diff suppressed because it is too large Load Diff
+448
View File
@@ -0,0 +1,448 @@
//! Implementations of the low-level parser combinators.
pub(crate) mod rfc;
use crate::format_description::modifier::Padding;
use crate::parsing::ParsedItem;
use crate::parsing::shim::Integer;
/// The sign of a number.
#[allow(
clippy::missing_docs_in_private_items,
reason = "self-explanatory variants"
)]
#[derive(Debug)]
pub(crate) enum Sign {
Negative,
Positive,
}
/// Parse a "+" or "-" sign.
#[inline]
pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, Sign>> {
match input {
[b'-', remaining @ ..] => Some(ParsedItem(remaining, Sign::Negative)),
[b'+', remaining @ ..] => Some(ParsedItem(remaining, Sign::Positive)),
_ => None,
}
}
/// Consume zero or more instances of the provided parser. The parser must return the unit value.
#[inline]
pub(crate) fn zero_or_more<P>(parser: P) -> impl for<'a> FnMut(&'a [u8]) -> ParsedItem<'a, ()>
where
P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
{
move |mut input| {
while let Some(remaining) = parser(input) {
input = remaining.into_inner();
}
ParsedItem(input, ())
}
}
/// Consume one of or more instances of the provided parser. The parser must produce the unit value.
#[inline]
pub(crate) fn one_or_more<P>(parser: P) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>
where
P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
{
move |mut input| {
input = parser(input)?.into_inner();
while let Some(remaining) = parser(input) {
input = remaining.into_inner();
}
Some(ParsedItem(input, ()))
}
}
/// Consume between `n` and `m` digits, returning the numerical value.
#[inline]
pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T>(
mut input: &[u8],
) -> Option<ParsedItem<'_, T>>
where
T: Integer,
{
const {
assert!(N > 0);
assert!(M >= N);
}
let mut value = T::ZERO;
// Mandatory
for i in 0..N {
let digit;
ParsedItem(input, digit) = any_digit(input)?;
if i != T::MAX_NUM_DIGITS - 1 {
value = value.push_digit(digit - b'0');
} else {
value = value.checked_push_digit(digit - b'0')?;
}
}
// Optional
for i in N..M {
let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
break;
};
input = new_input;
if i != T::MAX_NUM_DIGITS - 1 {
value = value.push_digit(digit - b'0');
} else {
value = value.checked_push_digit(digit - b'0')?;
}
}
Some(ParsedItem(input, value))
}
/// Consume one or two digits, returning the numerical value.
#[inline]
pub(crate) fn one_or_two_digits(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
match input {
[a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
let a = *a - b'0';
let b = *b - b'0';
Some(ParsedItem(remaining, a * 10 + b))
}
[a @ b'0'..=b'9', remaining @ ..] => {
let a = *a - b'0';
Some(ParsedItem(remaining, a))
}
_ => None,
}
}
/// Parse an exact number of digits without padding.
#[derive(Debug)]
pub(crate) struct ExactlyNDigits<const N: u8>;
impl ExactlyNDigits<1> {
/// Consume exactly one digit.
#[inline]
pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
match input {
[a @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *a - b'0')),
_ => None,
}
}
}
impl ExactlyNDigits<2> {
/// Consume exactly two digits.
#[inline]
pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
match input {
[a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
let a = *a - b'0';
let b = *b - b'0';
Some(ParsedItem(remaining, a * 10 + b))
}
_ => None,
}
}
}
impl ExactlyNDigits<3> {
/// Consume exactly three digits.
#[inline]
pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
match input {
[
a @ b'0'..=b'9',
b @ b'0'..=b'9',
c @ b'0'..=b'9',
remaining @ ..,
] => {
let a = (*a - b'0') as u16;
let b = (*b - b'0') as u16;
let c = (*c - b'0') as u16;
Some(ParsedItem(remaining, a * 100 + b * 10 + c))
}
_ => None,
}
}
}
impl ExactlyNDigits<4> {
/// Consume exactly four digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
let [a, b, c, d, remaining @ ..] = input else {
return None;
};
let digits = [a, b, c, d].map(|d| (*d as u16).wrapping_sub(b'0' as u16));
if digits.iter().any(|&digit| digit > 9) {
return None;
}
let value = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
Some(ParsedItem(remaining, value))
}
}
impl ExactlyNDigits<5> {
/// Consume exactly five digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
let [a, b, c, d, e, remaining @ ..] = input else {
return None;
};
let digits = [a, b, c, d, e].map(|d| (*d as u32).wrapping_sub(b'0' as u32));
if digits.iter().any(|&digit| digit > 9) {
return None;
}
let value =
digits[0] * 10_000 + digits[1] * 1_000 + digits[2] * 100 + digits[3] * 10 + digits[4];
Some(ParsedItem(remaining, value))
}
}
impl ExactlyNDigits<6> {
/// Consume exactly six digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
let [a, b, c, d, e, f, remaining @ ..] = input else {
return None;
};
// Calling `.map` successively results in slightly better codegen.
let digits = [a, b, c, d, e, f]
.map(|d| *d as u32)
.map(|d| d.wrapping_sub(b'0' as u32));
if digits.iter().any(|&digit| digit > 9) {
return None;
}
let value = digits[0] * 100_000
+ digits[1] * 10_000
+ digits[2] * 1_000
+ digits[3] * 100
+ digits[4] * 10
+ digits[5];
Some(ParsedItem(remaining, value))
}
}
impl ExactlyNDigits<7> {
/// Consume exactly seven digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
let [a, b, c, d, e, f, g, remaining @ ..] = input else {
return None;
};
// For whatever reason, the compiler does *not* autovectorize if `.map` is applied directly.
let mut digits = [*a, *b, *c, *d, *e, *f, *g];
digits = digits.map(|d| d.wrapping_sub(b'0'));
if digits.iter().any(|&digit| digit > 9) {
return None;
}
let value = digits[0] as u32 * 1_000_000
+ digits[1] as u32 * 100_000
+ digits[2] as u32 * 10_000
+ digits[3] as u32 * 1_000
+ digits[4] as u32 * 100
+ digits[5] as u32 * 10
+ digits[6] as u32;
Some(ParsedItem(remaining, value))
}
}
impl ExactlyNDigits<8> {
/// Consume exactly eight digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
let [a, b, c, d, e, f, g, h, remaining @ ..] = input else {
return None;
};
let mut digits = [*a, *b, *c, *d, *e, *f, *g, *h];
digits = [
digits[0].wrapping_sub(b'0'),
digits[1].wrapping_sub(b'0'),
digits[2].wrapping_sub(b'0'),
digits[3].wrapping_sub(b'0'),
digits[4].wrapping_sub(b'0'),
digits[5].wrapping_sub(b'0'),
digits[6].wrapping_sub(b'0'),
digits[7].wrapping_sub(b'0'),
];
if digits.iter().any(|&digit| digit > 9) {
return None;
}
let value = digits[0] as u32 * 10_000_000
+ digits[1] as u32 * 1_000_000
+ digits[2] as u32 * 100_000
+ digits[3] as u32 * 10_000
+ digits[4] as u32 * 1_000
+ digits[5] as u32 * 100
+ digits[6] as u32 * 10
+ digits[7] as u32;
Some(ParsedItem(remaining, value))
}
}
impl ExactlyNDigits<9> {
/// Consume exactly nine digits.
#[inline]
pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
let [a, b, c, d, e, f, g, h, i, remaining @ ..] = input else {
return None;
};
let mut digits = [*a, *b, *c, *d, *e, *f, *g, *h];
digits = [
digits[0] - b'0',
digits[1] - b'0',
digits[2] - b'0',
digits[3] - b'0',
digits[4] - b'0',
digits[5] - b'0',
digits[6] - b'0',
digits[7] - b'0',
];
let ones_digit = (*i as u32).wrapping_sub(b'0' as u32);
if digits.iter().any(|&digit| digit > 9) || ones_digit > 9 {
return None;
}
let value = digits[0] as u32 * 100_000_000
+ digits[1] as u32 * 10_000_000
+ digits[2] as u32 * 1_000_000
+ digits[3] as u32 * 100_000
+ digits[4] as u32 * 10_000
+ digits[5] as u32 * 1_000
+ digits[6] as u32 * 100
+ digits[7] as u32 * 10
+ ones_digit;
Some(ParsedItem(remaining, value))
}
}
/// Consume exactly `n` digits, returning the numerical value.
pub(crate) fn exactly_n_digits_padded<const N: u8, T>(
padding: Padding,
) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
where
T: Integer,
{
n_to_m_digits_padded::<N, N, _>(padding)
}
/// Consume between `n` and `m` digits, returning the numerical value.
pub(crate) fn n_to_m_digits_padded<const N: u8, const M: u8, T>(
padding: Padding,
) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
where
T: Integer,
{
const {
assert!(N > 0);
assert!(M >= N);
}
move |mut input| match padding {
Padding::None => n_to_m_digits::<1, M, _>(input),
Padding::Space => {
let mut value = T::ZERO;
// Consume the padding.
let mut pad_width = 0;
for _ in 0..(N - 1) {
match ascii_char::<b' '>(input) {
Some(parsed) => {
pad_width += 1;
input = parsed.0;
}
None => break,
}
}
// Mandatory
for i in 0..(N - pad_width) {
let digit;
ParsedItem(input, digit) = any_digit(input)?;
value = if i != T::MAX_NUM_DIGITS - 1 {
value.push_digit(digit - b'0')
} else {
value.checked_push_digit(digit - b'0')?
};
}
// Optional
for i in N..M {
let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
break;
};
input = new_input;
value = if i - pad_width != T::MAX_NUM_DIGITS - 1 {
value.push_digit(digit - b'0')
} else {
value.checked_push_digit(digit - b'0')?
};
}
Some(ParsedItem(input, value))
}
Padding::Zero => n_to_m_digits::<N, M, _>(input),
}
}
/// Consume exactly one digit.
#[inline]
pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
match input {
[c @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *c)),
_ => None,
}
}
/// Consume exactly one of the provided ASCII characters.
#[inline]
pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
const {
assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
}
match input {
[c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())),
_ => None,
}
}
/// Consume exactly one of the provided ASCII characters, case-insensitive.
#[inline]
pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
const {
assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
}
match input {
[c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())),
_ => None,
}
}
/// Optionally consume an input with a given parser.
#[inline]
pub(crate) fn opt<T>(
parser: impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
) -> impl for<'a> Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
move |input| match parser(input) {
Some(value) => value.map(Some),
None => ParsedItem(input, None),
}
}
@@ -0,0 +1,191 @@
//! Rules defined in [ISO 8601].
//!
//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
use core::num::NonZero;
use num_conv::prelude::*;
use crate::parsing::ParsedItem;
use crate::parsing::combinator::{ExactlyNDigits, Sign, any_digit, sign};
use crate::{Month, Weekday};
/// What kind of format is being parsed. This is used to ensure each part of the format (date, time,
/// offset) is the same kind.
#[derive(Debug, Clone, Copy)]
pub(crate) enum ExtendedKind {
/// The basic format.
Basic,
/// The extended format.
Extended,
/// ¯\_(ツ)_/¯
Unknown,
}
impl ExtendedKind {
/// Is it possible that the format is extended?
#[inline]
pub(crate) const fn maybe_extended(self) -> bool {
matches!(self, Self::Extended | Self::Unknown)
}
/// Is the format known for certain to be extended?
#[inline]
pub(crate) const fn is_extended(self) -> bool {
matches!(self, Self::Extended)
}
/// If the kind is `Unknown`, make it `Basic`. Otherwise, do nothing. Returns `Some` if and only
/// if the kind is now `Basic`.
#[inline]
pub(crate) const fn coerce_basic(&mut self) -> Option<()> {
match self {
Self::Basic => Some(()),
Self::Extended => None,
Self::Unknown => {
*self = Self::Basic;
Some(())
}
}
}
/// If the kind is `Unknown`, make it `Extended`. Otherwise, do nothing. Returns `Some` if and
/// only if the kind is now `Extended`.
#[inline]
pub(crate) const fn coerce_extended(&mut self) -> Option<()> {
match self {
Self::Basic => None,
Self::Extended => Some(()),
Self::Unknown => {
*self = Self::Extended;
Some(())
}
}
}
}
/// Parse a possibly expanded year.
#[inline]
pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> {
Some(match sign(input) {
Some(ParsedItem(input, sign)) => ExactlyNDigits::<6>::parse(input)?.map(|val| {
let val = val.cast_signed();
match sign {
Sign::Negative => -val,
Sign::Positive => val,
}
}),
None => ExactlyNDigits::<4>::parse(input)?.map(|val| val.cast_signed().widen()),
})
}
/// Parse a month.
#[inline]
pub(crate) fn month(input: &[u8]) -> Option<ParsedItem<'_, Month>> {
match input {
[b'0', b'1', remaining @ ..] => Some(ParsedItem(remaining, Month::January)),
[b'0', b'2', remaining @ ..] => Some(ParsedItem(remaining, Month::February)),
[b'0', b'3', remaining @ ..] => Some(ParsedItem(remaining, Month::March)),
[b'0', b'4', remaining @ ..] => Some(ParsedItem(remaining, Month::April)),
[b'0', b'5', remaining @ ..] => Some(ParsedItem(remaining, Month::May)),
[b'0', b'6', remaining @ ..] => Some(ParsedItem(remaining, Month::June)),
[b'0', b'7', remaining @ ..] => Some(ParsedItem(remaining, Month::July)),
[b'0', b'8', remaining @ ..] => Some(ParsedItem(remaining, Month::August)),
[b'0', b'9', remaining @ ..] => Some(ParsedItem(remaining, Month::September)),
[b'1', b'0', remaining @ ..] => Some(ParsedItem(remaining, Month::October)),
[b'1', b'1', remaining @ ..] => Some(ParsedItem(remaining, Month::November)),
[b'1', b'2', remaining @ ..] => Some(ParsedItem(remaining, Month::December)),
_ => None,
}
}
/// Parse a week number.
#[inline]
pub(crate) fn week(input: &[u8]) -> Option<ParsedItem<'_, NonZero<u8>>> {
ExactlyNDigits::<2>::parse(input).and_then(|parsed| parsed.flat_map(NonZero::new))
}
/// Parse a day of the month.
#[inline]
pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZero<u8>>> {
ExactlyNDigits::<2>::parse(input).and_then(|parsed| parsed.flat_map(NonZero::new))
}
/// Parse a day of the week.
#[inline]
pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> {
match input {
[b'1', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Monday)),
[b'2', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Tuesday)),
[b'3', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Wednesday)),
[b'4', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Thursday)),
[b'5', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Friday)),
[b'6', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Saturday)),
[b'7', remaining @ ..] => Some(ParsedItem(remaining, Weekday::Sunday)),
_ => None,
}
}
/// Parse a day of the year.
#[inline]
pub(crate) fn dayo(input: &[u8]) -> Option<ParsedItem<'_, NonZero<u16>>> {
ExactlyNDigits::<3>::parse(input).and_then(|parsed| parsed.flat_map(NonZero::new))
}
/// Parse the hour.
#[inline]
pub(crate) const fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
ExactlyNDigits::<2>::parse(input)
}
/// Parse the minute.
#[inline]
pub(crate) const fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
ExactlyNDigits::<2>::parse(input)
}
/// Parse a floating point number as its integer and optional fractional parts.
///
/// The number must have two digits before the decimal point. If a decimal point is present, at
/// least one digit must follow.
///
/// The return type is a tuple of the integer part and optional fraction part.
#[inline]
pub(crate) fn float(input: &[u8]) -> Option<ParsedItem<'_, (u8, Option<f64>)>> {
// Two digits before the decimal.
let ParsedItem(input, integer_part) = match input {
[
first_digit @ b'0'..=b'9',
second_digit @ b'0'..=b'9',
input @ ..,
] => ParsedItem(input, (first_digit - b'0') * 10 + (second_digit - b'0')),
_ => return None,
};
if let Some(ParsedItem(input, ())) = decimal_sign(input) {
// Mandatory post-decimal digit.
let ParsedItem(mut input, mut fractional_part) =
any_digit(input)?.map(|digit| ((digit - b'0') as f64) / 10.);
let mut divisor = 10.;
// Any number of subsequent digits.
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
input = new_input;
divisor *= 10.;
fractional_part += (digit - b'0') as f64 / divisor;
}
Some(ParsedItem(input, (integer_part, Some(fractional_part))))
} else {
Some(ParsedItem(input, (integer_part, None)))
}
}
/// Parse a "decimal sign", which is either a comma or a period.
#[inline]
fn decimal_sign(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
match input {
[b'.' | b',', remaining @ ..] => Some(ParsedItem(remaining, ())),
_ => None,
}
}
@@ -0,0 +1,10 @@
//! Combinators for rules as defined in a standard.
//!
//! When applicable, these rules have been converted strictly following the ABNF syntax as specified
//! in [RFC 2234].
//!
//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
pub(crate) mod iso8601;
pub(crate) mod rfc2234;
pub(crate) mod rfc2822;
@@ -0,0 +1,14 @@
//! Rules defined in [RFC 2234].
//!
//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
use crate::parsing::ParsedItem;
/// Consume exactly one space or tab.
#[inline]
pub(crate) const fn wsp(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
match input {
[b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())),
_ => None,
}
}
@@ -0,0 +1,268 @@
//! Rules defined in [RFC 2822].
//!
//! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
use num_conv::prelude::*;
use crate::parsing::ParsedItem;
use crate::parsing::combinator::rfc::rfc2234::wsp;
use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more};
const DEPTH_LIMIT: u8 = 32;
/// Consume the `fws` rule.
// The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/
#[inline]
pub(crate) fn fws(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
// Fast path for a single space followed by any ASCII character above space (the highest
// codepoint for ASCII whitespace).
if input.first() == Some(&b' ') && matches!(input.get(1), Some(0x21..)) {
return Some(ParsedItem(&input[1..], ()));
}
crate::hint::cold_path();
// Secondary fast path for single whitespace character followed by non-whitespace.
if !matches!(input.first(), Some(b'\r' | b' ' | b'\t')) {
return None;
}
if !matches!(input.get(1), Some(b' ' | b'\r' | b'\n' | b'\t')) {
return Some(ParsedItem(&input[1..], ()));
}
#[inline(never)]
fn fws_uncommon(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
if let [b'\r', b'\n', rest @ ..] = input {
one_or_more(wsp)(rest)
} else {
input = one_or_more(wsp)(input)?.into_inner();
while let [b'\r', b'\n', rest @ ..] = input {
input = one_or_more(wsp)(rest)?.into_inner();
}
Some(ParsedItem(input, ()))
}
}
fws_uncommon(input)
}
/// Consume the `cfws` rule.
// The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty.
#[inline]
pub(crate) fn cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
// Fast path for a single space followed by any ASCII character above the left parenthesis
// (which would start a comment).
if input.first() == Some(&b' ') && matches!(input.get(1), Some(0x29..)) {
return Some(ParsedItem(&input[1..], ()));
}
crate::hint::cold_path();
// Secondary fast path for whitespace other than a single space.
if input.first() != Some(&b'(') {
if !matches!(input.first(), Some(b'\r' | b' ' | b'\t')) {
return None;
}
if !matches!(input.get(1), Some(b'(' | b' ' | b'\r' | b'\n' | b'\t')) {
return Some(ParsedItem(&input[1..], ()));
}
}
#[inline(never)]
fn cfws_uncommon(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
one_or_more(|input| fws(input).or_else(|| comment(input, 1)))(input)
}
cfws_uncommon(input)
}
/// Optional `cfws` rule. This is equivalent to `opt(cfws)`, but is better optimized for the common
/// case where no `cfws` is present.
#[inline]
pub(crate) fn opt_cfws(input: &[u8]) -> ParsedItem<'_, ()> {
if matches!(input.first(), Some(0x29..)) {
ParsedItem(input, ())
} else {
cfws(input).unwrap_or(ParsedItem(input, ()))
}
}
/// Equivalent to `opt(cfws)`, `ascii_char::<b':'>`, and `opt(cfws)` called in sequence, but is
/// better optimized for the common case where no `cfws` is present.
#[inline]
pub(crate) fn opt_cfws_colon_opt_cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
if input.first() == Some(&b':') && matches!(input.get(1), Some(0x29..)) {
Some(ParsedItem(&input[1..], ()))
} else {
crate::hint::cold_path();
let input = opt_cfws(input).into_inner();
let input = ascii_char::<b':'>(input)?.into_inner();
let input = opt_cfws(input).into_inner();
Some(ParsedItem(input, ()))
}
}
/// Consume the `comment` rule.
#[inline]
fn comment(mut input: &[u8], depth: u8) -> Option<ParsedItem<'_, ()>> {
// Avoid stack exhaustion DoS by limiting recursion depth. This will cause highly-nested
// comments to fail parsing, but comments *at all* are incredibly rare in practice.
//
// The error from this will not be descriptive, but the rarity and near-certain maliciousness of
// such inputs makes this an acceptable trade-off.
if depth == DEPTH_LIMIT {
return None;
}
input = ascii_char::<b'('>(input)?.into_inner();
input = zero_or_more(fws)(input).into_inner();
while let Some(rest) = ccontent(input, depth + 1) {
input = rest.into_inner();
input = zero_or_more(fws)(input).into_inner();
}
input = ascii_char::<b')'>(input)?.into_inner();
Some(ParsedItem(input, ()))
}
/// Consume the `ccontent` rule.
#[inline]
fn ccontent(input: &[u8], depth: u8) -> Option<ParsedItem<'_, ()>> {
ctext(input)
.or_else(|| quoted_pair(input))
.or_else(|| comment(input, depth))
}
/// Consume the `ctext` rule.
#[expect(
clippy::unnecessary_lazy_evaluations,
reason = "rust-lang/rust-clippy#8522"
)]
#[inline]
fn ctext(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
no_ws_ctl(input).or_else(|| match input {
[33..=39 | 42..=91 | 93..=126, rest @ ..] => Some(ParsedItem(rest, ())),
_ => None,
})
}
/// Consume the `quoted_pair` rule.
#[inline]
fn quoted_pair(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
input = ascii_char::<b'\\'>(input)?.into_inner();
input = text(input).into_inner();
// If nothing is parsed by `text`, this means by hit the `obs-text` rule and nothing matched.
// This is technically a success, and we used to check the `obs-qp` rule to ensure everything
// possible was consumed. After further analysis, it was determined that this check was
// unnecessary due to `obs-text` wholly subsuming `obs-qp` in this context. For this reason, if
// `text` fails to parse anything, we consider it a success without further consideration.
Some(ParsedItem(input, ()))
}
/// Consume the `no_ws_ctl` rule.
#[inline]
const fn no_ws_ctl(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
match input {
[1..=8 | 11..=12 | 14..=31 | 127, rest @ ..] => Some(ParsedItem(rest, ())),
_ => None,
}
}
/// Consume the `text` rule.
#[inline]
fn text<'a>(input: &'a [u8]) -> ParsedItem<'a, ()> {
let new_text = |input: &'a [u8]| match input {
[1..=9 | 11..=12 | 14..=127, rest @ ..] => Some(ParsedItem(rest, ())),
_ => None,
};
let obs_char = |input: &'a [u8]| match input {
// This is technically allowed, but consuming this would mean the rest of the string is
// eagerly consumed without consideration for where the comment actually ends.
[b')', ..] => None,
[0..=9 | 11..=12 | 14..=127, rest @ ..] => Some(rest),
_ => None,
};
let obs_text = |mut input| {
input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner();
input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner();
while let Some(rest) = obs_char(input) {
input = rest;
input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner();
input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner();
}
ParsedItem(input, ())
};
new_text(input).unwrap_or_else(|| obs_text(input))
}
/// Consume an old zone literal, returning the offset in hours.
pub(crate) fn zone_literal(input: &[u8]) -> Option<ParsedItem<'_, i8>> {
let [first, second, third, rest @ ..] = input else {
const UT_VARIANTS: [u16; 4] = [
u16::from_ne_bytes(*b"ut"),
u16::from_ne_bytes(*b"uT"),
u16::from_ne_bytes(*b"Ut"),
u16::from_ne_bytes(*b"UT"),
];
let [first, rest @ ..] = input else {
return None;
};
if let [second, rest @ ..] = rest
&& UT_VARIANTS.contains(&u16::from_ne_bytes([*first, *second]))
{
return Some(ParsedItem(rest, 0));
}
return (*first != b'j' && *first != b'J' && first.is_ascii_alphabetic())
.then_some(ParsedItem(rest, 0));
};
let byte = u32::from_ne_bytes([
0,
first.to_ascii_lowercase(),
second.to_ascii_lowercase(),
third.to_ascii_lowercase(),
]);
const ZONES: [u32; 8] = [
u32::from_ne_bytes([0, b'e', b's', b't']),
u32::from_ne_bytes([0, b'e', b'd', b't']),
u32::from_ne_bytes([0, b'c', b's', b't']),
u32::from_ne_bytes([0, b'c', b'd', b't']),
u32::from_ne_bytes([0, b'm', b's', b't']),
u32::from_ne_bytes([0, b'm', b'd', b't']),
u32::from_ne_bytes([0, b'p', b's', b't']),
u32::from_ne_bytes([0, b'p', b'd', b't']),
];
let eq = [
if ZONES[0] == byte { i32::MAX } else { 0 },
if ZONES[1] == byte { i32::MAX } else { 0 },
if ZONES[2] == byte { i32::MAX } else { 0 },
if ZONES[3] == byte { i32::MAX } else { 0 },
if ZONES[4] == byte { i32::MAX } else { 0 },
if ZONES[5] == byte { i32::MAX } else { 0 },
if ZONES[6] == byte { i32::MAX } else { 0 },
if ZONES[7] == byte { i32::MAX } else { 0 },
];
if eq == [0; 8] && byte != const { u32::from_ne_bytes([0, b'g', b'm', b't']) } {
return None;
}
let nonzero_zones = [
eq[0] & -5,
eq[1] & -4,
eq[2] & -6,
eq[3] & -5,
eq[4] & -7,
eq[5] & -6,
eq[6] & -8,
eq[7] & -7,
];
let zone = nonzero_zones.iter().sum::<i32>().truncate();
Some(ParsedItem(rest, zone))
}
+813
View File
@@ -0,0 +1,813 @@
//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
use core::num::NonZero;
use num_conv::prelude::*;
use crate::format_description::{Period, modifier};
use crate::parsing::ParsedItem;
use crate::parsing::combinator::{
ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, n_to_m_digits, n_to_m_digits_padded,
opt, sign,
};
use crate::unit::*;
use crate::{Month, Weekday};
/// Parse the full calendar-based year.
///
/// This permits utilizing the full range of supported years, though at the cost of introducing
/// parsing ambiguities.
#[inline]
pub(crate) fn parse_calendar_year_full_extended_range(
input: &[u8],
modifiers: modifier::CalendarYearFullExtendedRange,
) -> Option<ParsedItem<'_, i32>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => -year.cast_signed(),
Sign::Positive => year.cast_signed(),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(input, year.cast_signed()))
}
}
/// Parse the full calendar-based year.
///
/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
/// full range of supported years when the `large-dates` feature flag is enabled.
#[inline]
pub(crate) fn parse_calendar_year_full_standard_range(
input: &[u8],
modifiers: modifier::CalendarYearFullStandardRange,
) -> Option<ParsedItem<'_, i32>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => -year.cast_signed(),
Sign::Positive => year.cast_signed(),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(input, year.cast_signed()))
}
}
/// Parse the full ISO-week based year.
///
/// This permits utilizing the full range of supported years, though at the cost of introducing
/// parsing ambiguities.
#[inline]
pub(crate) fn parse_iso_year_full_extended_range(
input: &[u8],
modifiers: modifier::IsoYearFullExtendedRange,
) -> Option<ParsedItem<'_, i32>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => -year.cast_signed(),
Sign::Positive => year.cast_signed(),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(input, year.cast_signed()))
}
}
/// Parse the full ISO-week based year.
///
/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
/// full range of supported years when the `large-dates` feature flag is enabled.
#[inline]
pub(crate) fn parse_iso_year_full_standard_range(
input: &[u8],
modifiers: modifier::IsoYearFullStandardRange,
) -> Option<ParsedItem<'_, i32>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => -year.cast_signed(),
Sign::Positive => year.cast_signed(),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
Some(ParsedItem(input, year.cast_signed()))
}
}
/// Parse all digits of the calendar-based year except the last two.
///
/// This permits utilizing the full range of supported years, though at the cost of introducing
/// parsing ambiguities.
#[inline]
pub(crate) fn parse_calendar_year_century_extended_range(
input: &[u8],
modifiers: modifier::CalendarYearCenturyExtendedRange,
) -> Option<ParsedItem<'_, (i16, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => (-year.cast_signed(), true),
Sign::Positive => (year.cast_signed(), false),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(input, (year.cast_signed(), false)))
}
}
/// Parse all digits of the calendar-based year except the last two.
///
/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
/// full range of supported years when the `large-dates` feature flag is enabled.
#[inline]
pub(crate) fn parse_calendar_year_century_standard_range(
input: &[u8],
modifiers: modifier::CalendarYearCenturyStandardRange,
) -> Option<ParsedItem<'_, (i16, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => (-year.cast_signed(), true),
Sign::Positive => (year.cast_signed(), false),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(input, (year.cast_signed(), false)))
}
}
/// Parse all digits of the ISO week-based year except the last two.
///
/// This permits utilizing the full range of supported years, though at the cost of introducing
/// parsing ambiguities.
#[inline]
pub(crate) fn parse_iso_year_century_extended_range(
input: &[u8],
modifiers: modifier::IsoYearCenturyExtendedRange,
) -> Option<ParsedItem<'_, (i16, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => (-year.cast_signed(), true),
Sign::Positive => (year.cast_signed(), false),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(input, (year.cast_signed(), false)))
}
}
/// Parse all digits of the ISO week-based year except the last two.
///
/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
/// full range of supported years when the `large-dates` feature flag is enabled.
#[inline]
pub(crate) fn parse_iso_year_century_standard_range(
input: &[u8],
modifiers: modifier::IsoYearCenturyStandardRange,
) -> Option<ParsedItem<'_, (i16, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
if let Some(sign) = sign {
let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(
input,
match sign {
Sign::Negative => (-year.cast_signed(), true),
Sign::Positive => (year.cast_signed(), false),
},
))
} else if modifiers.sign_is_mandatory {
None
} else {
let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
Some(ParsedItem(input, (year.cast_signed(), false)))
}
}
/// Parse the last two digits of the calendar-based year.
#[inline]
pub(crate) fn parse_calendar_year_last_two(
input: &[u8],
modifiers: modifier::CalendarYearLastTwo,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the last two digits of the ISO week-based year.
#[inline]
pub(crate) fn parse_iso_year_last_two(
input: &[u8],
modifiers: modifier::IsoYearLastTwo,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "month" component of a `Date` in the abbreviated form (e.g. "Jan").
#[inline]
pub(crate) fn parse_month_short(
input: &[u8],
modifiers: modifier::MonthShort,
) -> Option<ParsedItem<'_, Month>> {
let [first, second, third, rest @ ..] = input else {
return None;
};
let byte = if modifiers.case_sensitive {
u32::from_ne_bytes([0, *first, *second, *third])
} else {
u32::from_ne_bytes([
0,
first.to_ascii_uppercase(),
second.to_ascii_lowercase(),
third.to_ascii_lowercase(),
])
};
const WEEKDAYS: [u32; 12] = [
u32::from_ne_bytes([0, b'J', b'a', b'n']),
u32::from_ne_bytes([0, b'F', b'e', b'b']),
u32::from_ne_bytes([0, b'M', b'a', b'r']),
u32::from_ne_bytes([0, b'A', b'p', b'r']),
u32::from_ne_bytes([0, b'M', b'a', b'y']),
u32::from_ne_bytes([0, b'J', b'u', b'n']),
u32::from_ne_bytes([0, b'J', b'u', b'l']),
u32::from_ne_bytes([0, b'A', b'u', b'g']),
u32::from_ne_bytes([0, b'S', b'e', b'p']),
u32::from_ne_bytes([0, b'O', b'c', b't']),
u32::from_ne_bytes([0, b'N', b'o', b'v']),
u32::from_ne_bytes([0, b'D', b'e', b'c']),
];
let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
| ((WEEKDAYS[1] == byte) as u32) << 2
| ((WEEKDAYS[2] == byte) as u32) << 3
| ((WEEKDAYS[3] == byte) as u32) << 4
| ((WEEKDAYS[4] == byte) as u32) << 5
| ((WEEKDAYS[5] == byte) as u32) << 6
| ((WEEKDAYS[6] == byte) as u32) << 7
| ((WEEKDAYS[7] == byte) as u32) << 8
| ((WEEKDAYS[8] == byte) as u32) << 9
| ((WEEKDAYS[9] == byte) as u32) << 10
| ((WEEKDAYS[10] == byte) as u32) << 11
| ((WEEKDAYS[11] == byte) as u32) << 12;
if bitmask == 0 {
return None;
}
let index = if cfg!(target_endian = "little") {
bitmask.trailing_zeros() as u8
} else {
31 - bitmask.leading_zeros() as u8
};
// Safety: `index` cannot be greater than 12 because there are only 12 elements in the
// array that is converted to a bitmask. We know at least one element matched because
// the bitmask is non-zero.
let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
Some(ParsedItem(rest, month))
}
/// Parse the "month" component of a `Date` in the long form (e.g. "January").
#[inline]
pub(crate) fn parse_month_long(
input: &[u8],
modifiers: modifier::MonthLong,
) -> Option<ParsedItem<'_, Month>> {
use Month::*;
let ParsedItem(rest, month) = parse_month_short(
input,
modifier::MonthShort {
case_sensitive: modifiers.case_sensitive,
},
)?;
let expected_remaining = match month {
January => b"uary".as_slice(),
February => b"ruary".as_slice(),
March => b"ch".as_slice(),
April => b"il".as_slice(),
May => b"".as_slice(),
June => b"e".as_slice(),
July => b"y".as_slice(),
August => b"ust".as_slice(),
September => b"tember".as_slice(),
October => b"ober".as_slice(),
November | December => b"ember".as_slice(),
};
if modifiers.case_sensitive {
rest.strip_prefix(expected_remaining)
.map(|remaining| ParsedItem(remaining, month))
} else {
let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
core::iter::zip(head, expected_remaining)
.all(|(a, b)| a.eq_ignore_ascii_case(b))
.then_some(ParsedItem(tail, month))
}
}
/// Parse the "month" component of a `Date` in the numerical format (e.g. "1" for January).
#[inline]
pub(crate) fn parse_month_numerical(
input: &[u8],
modifiers: modifier::MonthNumerical,
) -> Option<ParsedItem<'_, Month>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
.flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
}
/// Parse the "week number" component of a `Date`, where week 1 starts on the last Monday on or
/// before January 4.
#[inline]
pub(crate) fn parse_week_number_iso(
input: &[u8],
modifiers: modifier::WeekNumberIso,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "week number" component of a `Date`, where week 1 starts on the first Sunday of the
/// year.
#[inline]
pub(crate) fn parse_week_number_sunday(
input: &[u8],
modifiers: modifier::WeekNumberSunday,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "week number" component of a `Date`, where week 1 starts on the first Monday of the
/// year.
#[inline]
pub(crate) fn parse_week_number_monday(
input: &[u8],
modifiers: modifier::WeekNumberMonday,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "weekday" component of a `Date` in the abbreviated form (e.g. "Mon").
#[inline]
pub(crate) fn parse_weekday_short(
input: &[u8],
modifiers: modifier::WeekdayShort,
) -> Option<ParsedItem<'_, Weekday>> {
let [first, second, third, rest @ ..] = input else {
return None;
};
let byte = if modifiers.case_sensitive {
u32::from_ne_bytes([0, *first, *second, *third])
} else {
u32::from_ne_bytes([
0,
first.to_ascii_uppercase(),
second.to_ascii_lowercase(),
third.to_ascii_lowercase(),
])
};
const WEEKDAYS: [u32; 7] = [
u32::from_ne_bytes([0, b'M', b'o', b'n']),
u32::from_ne_bytes([0, b'T', b'u', b'e']),
u32::from_ne_bytes([0, b'W', b'e', b'd']),
u32::from_ne_bytes([0, b'T', b'h', b'u']),
u32::from_ne_bytes([0, b'F', b'r', b'i']),
u32::from_ne_bytes([0, b'S', b'a', b't']),
u32::from_ne_bytes([0, b'S', b'u', b'n']),
];
let bitmask = ((WEEKDAYS[0] == byte) as u32)
| ((WEEKDAYS[1] == byte) as u32) << 1
| ((WEEKDAYS[2] == byte) as u32) << 2
| ((WEEKDAYS[3] == byte) as u32) << 3
| ((WEEKDAYS[4] == byte) as u32) << 4
| ((WEEKDAYS[5] == byte) as u32) << 5
| ((WEEKDAYS[6] == byte) as u32) << 6;
if bitmask == 0 {
return None;
}
let index = if cfg!(target_endian = "little") {
bitmask.trailing_zeros()
} else {
31 - bitmask.leading_zeros()
};
if index > 6 {
return None;
}
// Safety: Values zero thru six are valid variants, while values greater than six have
// already been excluded above. We know at least one element matched because the bitmask
// is non-zero.
let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
Some(ParsedItem(rest, weekday))
}
/// Parse the "weekday" component of a `Date` in the long form (e.g. "Monday").
#[inline]
pub(crate) fn parse_weekday_long(
input: &[u8],
modifiers: modifier::WeekdayLong,
) -> Option<ParsedItem<'_, Weekday>> {
let ParsedItem(rest, weekday) = parse_weekday_short(
input,
modifier::WeekdayShort {
case_sensitive: modifiers.case_sensitive,
},
)?;
let expected_remaining = match weekday {
Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
Weekday::Tuesday => b"sday".as_slice(),
Weekday::Wednesday => b"nesday".as_slice(),
Weekday::Thursday => b"rsday".as_slice(),
Weekday::Saturday => b"urday".as_slice(),
};
if modifiers.case_sensitive {
rest.strip_prefix(expected_remaining)
.map(|remaining| ParsedItem(remaining, weekday))
} else {
let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
core::iter::zip(head, expected_remaining)
.all(|(a, b)| a.eq_ignore_ascii_case(b))
.then_some(ParsedItem(tail, weekday))
}
}
/// Parse the weekday component of a `Date` in the numerical format, where Sunday is the first day
/// of the week.`
#[inline]
pub(crate) fn parse_weekday_sunday(
input: &[u8],
modifiers: modifier::WeekdaySunday,
) -> Option<ParsedItem<'_, Weekday>> {
let [digit, rest @ ..] = input else {
return None;
};
let mut digit = digit
.wrapping_sub(b'0')
.wrapping_sub(u8::from(modifiers.one_indexed));
if digit > 6 {
return None;
}
// Remap so that Sunday comes after Saturday, not before Monday.
digit = (digit + 6) % 7;
// Safety: Values zero thru six are valid variants.
let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
Some(ParsedItem(rest, weekday))
}
/// Parse the weekday component of a `Date` in the numerical format, where Monday is the first day
/// of the week.`
#[inline]
pub(crate) fn parse_weekday_monday(
input: &[u8],
modifiers: modifier::WeekdayMonday,
) -> Option<ParsedItem<'_, Weekday>> {
let [digit, rest @ ..] = input else {
return None;
};
let digit = digit
.wrapping_sub(b'0')
.wrapping_sub(u8::from(modifiers.one_indexed));
if digit > 6 {
return None;
}
// Safety: Values zero thru six are valid variants.
let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
Some(ParsedItem(rest, weekday))
}
/// Parse the "ordinal" component of a `Date`.
#[inline]
pub(crate) fn parse_ordinal(
input: &[u8],
modifiers: modifier::Ordinal,
) -> Option<ParsedItem<'_, NonZero<u16>>> {
exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
.and_then(|parsed| parsed.flat_map(NonZero::new))
}
/// Parse the "day" component of a `Date`.
#[inline]
pub(crate) fn parse_day(
input: &[u8],
modifiers: modifier::Day,
) -> Option<ParsedItem<'_, NonZero<u8>>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
.and_then(|parsed| parsed.flat_map(NonZero::new))
}
/// Parse the "hour" component of a `Time` in the 12-hour format.
#[inline]
pub(crate) fn parse_hour_12(
input: &[u8],
modifiers: modifier::Hour12,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "hour" component of a `Time` in the 24-hour format.
#[inline]
pub(crate) fn parse_hour_24(
input: &[u8],
modifiers: modifier::Hour24,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "minute" component of a `Time`.
#[inline]
pub(crate) fn parse_minute(
input: &[u8],
modifiers: modifier::Minute,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "second" component of a `Time`.
#[inline]
pub(crate) fn parse_second(
input: &[u8],
modifiers: modifier::Second,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
#[inline]
pub(crate) fn parse_period(
input: &[u8],
modifiers: modifier::Period,
) -> Option<ParsedItem<'_, Period>> {
let [first, second, rest @ ..] = input else {
return None;
};
let mut first = *first;
let mut second = *second;
if modifiers.is_uppercase && modifiers.case_sensitive {
match [first, second].as_slice() {
b"AM" => Some(ParsedItem(rest, Period::Am)),
b"PM" => Some(ParsedItem(rest, Period::Pm)),
_ => None,
}
} else {
first = first.to_ascii_lowercase();
second = second.to_ascii_lowercase();
match &[first, second] {
b"am" => Some(ParsedItem(rest, Period::Am)),
b"pm" => Some(ParsedItem(rest, Period::Pm)),
_ => None,
}
}
}
/// Parse the "subsecond" component of a `Time`.
pub(crate) fn parse_subsecond(
input: &[u8],
modifiers: modifier::Subsecond,
) -> Option<ParsedItem<'_, u32>> {
use modifier::SubsecondDigits::*;
Some(match modifiers.digits {
One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.widen::<u32>() * 100_000_000),
Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.widen::<u32>() * 10_000_000),
Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.widen::<u32>() * 1_000_000),
Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.widen::<u32>() * 100_000),
Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
Nine => ExactlyNDigits::<9>::parse(input)?,
OneOrMore => {
const MULTIPLIERS: [u32; 8] =
[10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
// Consume the first digit, which is mandatory.
let ParsedItem(mut input, mut value) =
any_digit(input)?.map(|v| (v - b'0').widen::<u32>() * 100_000_000);
// Consume digits 2 thru 9, all of which are optional.
for multiplier in MULTIPLIERS {
if let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0').widen::<u32>() * multiplier;
input = new_input;
} else {
break;
}
}
// Consume any remaining digits, up to 32 total to avoid consuming unbounded user input.
// As these digits are past the nanosecond precision supported, they don't need to be
// involved in calculating the value.
for _ in 10..33 {
if let Some(ParsedItem(new_input, _)) = any_digit(input) {
input = new_input;
} else {
break;
}
}
ParsedItem(input, value)
}
})
}
/// Parse the "hour" component of a `UtcOffset`.
///
/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
#[inline]
pub(crate) fn parse_offset_hour(
input: &[u8],
modifiers: modifier::OffsetHour,
) -> Option<ParsedItem<'_, (i8, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
match sign {
Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, (hour.cast_signed(), false))),
}
}
/// Parse the "minute" component of a `UtcOffset`.
#[inline]
pub(crate) fn parse_offset_minute(
input: &[u8],
modifiers: modifier::OffsetMinute,
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_minute| offset_minute.cast_signed()),
)
}
/// Parse the "second" component of a `UtcOffset`.
#[inline]
pub(crate) fn parse_offset_second(
input: &[u8],
modifiers: modifier::OffsetSecond,
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_second| offset_second.cast_signed()),
)
}
/// Ignore the given number of bytes.
#[inline]
pub(crate) fn parse_ignore(
input: &[u8],
modifiers: modifier::Ignore,
) -> Option<ParsedItem<'_, ()>> {
let modifier::Ignore { count } = modifiers;
let input = input.get((count.get().widen())..)?;
Some(ParsedItem(input, ()))
}
/// Parse the Unix timestamp component with second precision, returning the value in nanoseconds.
#[inline]
pub(crate) fn parse_unix_timestamp_second(
input: &[u8],
modifiers: modifier::UnixTimestampSecond,
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) =
n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second));
match sign {
Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the Unix timestamp component with millisecond precision, returning the value in
/// nanoseconds.
#[inline]
pub(crate) fn parse_unix_timestamp_millisecond(
input: &[u8],
modifiers: modifier::UnixTimestampMillisecond,
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 17, u128>(input)?
.map(|val| val * Nanosecond::per_t::<u128>(Millisecond));
match sign {
Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the Unix timestamp component with microsecond precision, returning the value in
/// nanoseconds.
#[inline]
pub(crate) fn parse_unix_timestamp_microsecond(
input: &[u8],
modifiers: modifier::UnixTimestampMicrosecond,
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 20, u128>(input)?
.map(|val| val * Nanosecond::per_t::<u128>(Microsecond));
match sign {
Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the Unix timestamp component with nanosecond precision.
#[inline]
pub(crate) fn parse_unix_timestamp_nanosecond(
input: &[u8],
modifiers: modifier::UnixTimestampNanosecond,
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 23, u128>(input)?;
match sign {
Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the `end` component, which represents the end of input. If any input is remaining _and_
/// trailing input is prohibited, `None` is returned. If trailing input is permitted, it is
/// discarded.
#[inline]
pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
let modifier::End { trailing_input } = end;
if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
Some(ParsedItem(b"", ()))
} else {
None
}
}
+356
View File
@@ -0,0 +1,356 @@
//! Parse parts of an ISO 8601-formatted value.
use crate::error;
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::format_description::well_known::Iso8601;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::internal_macros::try_likely_ok;
use crate::parsing::combinator::rfc::iso8601::{
ExtendedKind, day, dayk, dayo, float, hour, min, month, week, year,
};
use crate::parsing::combinator::{Sign, ascii_char, sign};
use crate::parsing::{Parsed, ParsedItem};
use crate::unit::*;
impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
// Basic: [year][month][day]
// Extended: [year]["-"][month]["-"][day]
// Basic: [year][dayo]
// Extended: [year]["-"][dayo]
// Basic: [year]["W"][week][dayk]
// Extended: [year]["-"]["W"][week]["-"][dayk]
/// Parse a date in the basic or extended format. Reduced precision is permitted.
pub(crate) fn parse_date<'a>(
parsed: &'a mut Parsed,
extended_kind: &'a mut ExtendedKind,
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + use<'a, CONFIG> {
move |input| {
// Same for any acceptable format.
let ParsedItem(mut input, year) =
try_likely_ok!(year(input).ok_or(InvalidComponent("year")));
*extended_kind = match ascii_char::<b'-'>(input) {
Some(ParsedItem(new_input, ())) => {
input = new_input;
ExtendedKind::Extended
}
None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
};
let parsed_month_day = (|| {
let ParsedItem(mut input, month) =
try_likely_ok!(month(input).ok_or(InvalidComponent("month")));
if extended_kind.is_extended() {
input = try_likely_ok!(ascii_char::<b'-'>(input).ok_or(InvalidLiteral))
.into_inner();
}
let ParsedItem(input, day) =
try_likely_ok!(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 = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(parsed.with_year(year).ok_or(InvalidComponent("year")))
.with_month(month)
.ok_or(InvalidComponent("month"))
)
.with_day(day)
.ok_or(InvalidComponent("day"))
);
return Ok(input);
}
Err(err) => err,
};
// Don't check for `None`, as the error from year-month-day will always take priority.
if let Some(ParsedItem(input, ordinal)) = dayo(input) {
*parsed = try_likely_ok!(
try_likely_ok!(parsed.with_year(year).ok_or(InvalidComponent("year")))
.with_ordinal(ordinal)
.ok_or(InvalidComponent("ordinal"))
);
return Ok(input);
}
let parsed_week_weekday = (|| {
let input =
try_likely_ok!(ascii_char::<b'W'>(input).ok_or((false, InvalidLiteral)))
.into_inner();
let ParsedItem(mut input, week) =
try_likely_ok!(week(input).ok_or((true, InvalidComponent("week"))));
if extended_kind.is_extended() {
input = try_likely_ok!(ascii_char::<b'-'>(input).ok_or((true, InvalidLiteral)))
.into_inner();
}
let ParsedItem(input, weekday) =
try_likely_ok!(dayk(input).ok_or((true, InvalidComponent("weekday"))));
Ok(ParsedItem(input, (week, weekday)))
})();
match parsed_week_weekday {
Ok(ParsedItem(input, (week, weekday))) => {
*parsed = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(
parsed.with_iso_year(year).ok_or(InvalidComponent("year"))
)
.with_iso_week_number(week)
.ok_or(InvalidComponent("week"))
)
.with_weekday(weekday)
.ok_or(InvalidComponent("weekday"))
);
return Ok(input);
}
Err((false, _err)) => {}
// This error is more accurate than the one from year-month-day.
Err((true, err)) => ret_error = err,
}
Err(ret_error.into())
}
}
// Basic: ["T"][hour][min][sec]
// Extended: ["T"][hour][":"][min][":"][sec]
// Reduced precision: components after [hour] (including their preceding separator) can be
// omitted. ["T"] can be omitted if there is no date present.
/// Parse a time in the basic or extended format. Reduced precision is permitted.
pub(crate) fn parse_time<'a>(
parsed: &'a mut Parsed,
extended_kind: &'a mut ExtendedKind,
date_is_present: bool,
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + use<'a, CONFIG> {
move |mut input| {
match ascii_char::<b'T'>(input) {
Some(parsed) => input = parsed.into_inner(),
None if date_is_present => return Err(InvalidLiteral.into()),
None => {}
}
let ParsedItem(mut input, hour) =
try_likely_ok!(float(input).ok_or(InvalidComponent("hour")));
match hour {
(hour, None) => {
try_likely_ok!(parsed.set_hour_24(hour).ok_or(InvalidComponent("hour")))
}
(hour, Some(fractional_part)) => {
*parsed = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(
try_likely_ok!(
parsed.with_hour_24(hour).ok_or(InvalidComponent("hour"))
)
.with_minute((fractional_part * Second::per_t::<f64>(Minute)) as u8)
.ok_or(InvalidComponent("minute"))
)
.with_second(
(fractional_part * Second::per_t::<f64>(Hour)
% Minute::per_t::<f64>(Hour))
as u8,
)
.ok_or(InvalidComponent("second"))
)
.with_subsecond(
(fractional_part * Nanosecond::per_t::<f64>(Hour)
% Nanosecond::per_t::<f64>(Second))
as u32,
)
.ok_or(InvalidComponent("subsecond"))
);
return Ok(input);
}
};
if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
try_likely_ok!(
extended_kind
.coerce_extended()
.ok_or(InvalidComponent("minute"))
);
input = new_input;
};
let mut input = match float(input) {
Some(ParsedItem(input, (minute, None))) => {
extended_kind.coerce_basic();
try_likely_ok!(parsed.set_minute(minute).ok_or(InvalidComponent("minute")));
input
}
Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
// `None` is valid behavior, so don't error if this fails.
extended_kind.coerce_basic();
*parsed = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(
parsed.with_minute(minute).ok_or(InvalidComponent("minute"))
)
.with_second((fractional_part * Second::per_t::<f64>(Minute)) as u8)
.ok_or(InvalidComponent("second"))
)
.with_subsecond(
(fractional_part * Nanosecond::per_t::<f64>(Minute)
% Nanosecond::per_t::<f64>(Second))
as u32,
)
.ok_or(InvalidComponent("subsecond"))
);
return Ok(input);
}
// colon was present, so minutes are required
None if extended_kind.is_extended() => {
return Err(error::Parse::ParseFromDescription(InvalidComponent(
"minute",
)));
}
None => {
// Missing components are assumed to be zero.
*parsed = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(parsed.with_minute(0).ok_or(InvalidComponent("minute")))
.with_second(0)
.ok_or(InvalidComponent("second"))
)
.with_subsecond(0)
.ok_or(InvalidComponent("subsecond"))
);
return Ok(input);
}
};
if extended_kind.is_extended() {
match ascii_char::<b':'>(input) {
Some(ParsedItem(new_input, ())) => input = new_input,
None => {
*parsed = try_likely_ok!(
try_likely_ok!(parsed.with_second(0).ok_or(InvalidComponent("second")))
.with_subsecond(0)
.ok_or(InvalidComponent("subsecond"))
);
return Ok(input);
}
}
}
let (input, second, subsecond) = match float(input) {
Some(ParsedItem(input, (second, None))) => (input, second, 0),
Some(ParsedItem(input, (second, Some(fractional_part)))) => (
input,
second,
round(fractional_part * Nanosecond::per_t::<f64>(Second)) as u32,
),
None if extended_kind.is_extended() => {
return Err(error::Parse::ParseFromDescription(InvalidComponent(
"second",
)));
}
// Missing components are assumed to be zero.
None => (input, 0, 0),
};
*parsed = try_likely_ok!(
try_likely_ok!(parsed.with_second(second).ok_or(InvalidComponent("second")))
.with_subsecond(subsecond)
.ok_or(InvalidComponent("subsecond"))
);
Ok(input)
}
}
// Basic: [±][hour][min] or ["Z"]
// Extended: [±][hour][":"][min] or ["Z"]
// Reduced precision: [±][hour] or ["Z"]
/// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
pub(crate) fn parse_offset<'a>(
parsed: &'a mut Parsed,
extended_kind: &'a mut ExtendedKind,
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + use<'a, CONFIG> {
move |input| {
if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
*parsed = try_likely_ok!(
try_likely_ok!(
try_likely_ok!(
parsed
.with_offset_hour(0)
.ok_or(InvalidComponent("offset hour"))
)
.with_offset_minute_signed(0)
.ok_or(InvalidComponent("offset minute"))
)
.with_offset_second_signed(0)
.ok_or(InvalidComponent("offset second"))
);
return Ok(input);
}
let ParsedItem(input, sign) =
try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
let mut input = try_likely_ok!(
hour(input)
.and_then(|parsed_item| {
parsed_item.consume_value(|hour| {
parsed.set_offset_hour(match sign {
Sign::Negative => -hour.cast_signed(),
Sign::Positive => hour.cast_signed(),
})
})
})
.ok_or(InvalidComponent("offset hour"))
);
if extended_kind.maybe_extended()
&& let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input)
{
try_likely_ok!(
extended_kind
.coerce_extended()
.ok_or(InvalidComponent("offset minute"))
);
input = new_input;
};
match min(input) {
Some(ParsedItem(new_input, min)) => {
input = new_input;
try_likely_ok!(
parsed
.set_offset_minute_signed(match sign {
Sign::Negative => -min.cast_signed(),
Sign::Positive => min.cast_signed(),
})
.ok_or(InvalidComponent("offset minute"))
);
}
None => {
// Omitted offset minute is assumed to be zero.
parsed.set_offset_minute_signed(0);
}
}
// If `:` was present, the format has already been set to extended. As such, this call
// will do nothing in that case. If there wasn't `:` but minutes were
// present, we know it's the basic format. Do not use `?` on the call, as
// returning `None` is valid behavior.
extended_kind.coerce_basic();
Ok(input)
}
}
}
/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
/// implementation for `no_std`
#[inline]
fn round(value: f64) -> f64 {
#[cfg(feature = "std")]
{
value.round()
}
#[cfg(not(feature = "std"))]
{
debug_assert!(value.is_sign_positive() && !value.is_nan());
let f = value % 1.;
if f < 0.5 { value - f } else { value - f + 1. }
}
}
+61
View File
@@ -0,0 +1,61 @@
//! Parsing for various types.
pub(crate) mod combinator;
pub(crate) mod component;
mod iso8601;
pub(crate) mod parsable;
mod parsed;
pub(crate) mod shim;
pub use self::parsable::Parsable;
pub use self::parsed::Parsed;
/// An item that has been parsed. Represented as a `(remaining, value)` pair.
#[derive(Debug)]
pub(crate) struct ParsedItem<'a, T>(pub(crate) &'a [u8], pub(crate) T);
impl<'a, T> ParsedItem<'a, T> {
/// Map the value to a new value, preserving the remaining input.
#[inline]
pub(crate) fn map<U>(self, f: impl FnOnce(T) -> U) -> ParsedItem<'a, U> {
ParsedItem(self.0, f(self.1))
}
/// Map the value to a new, optional value, preserving the remaining input.
#[inline]
pub(crate) fn flat_map<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<ParsedItem<'a, U>> {
Some(ParsedItem(self.0, f(self.1)?))
}
/// Consume the stored value with the provided function. The remaining input is returned.
#[must_use = "this returns the remaining input"]
#[inline]
pub(crate) fn consume_value(self, f: impl FnOnce(T) -> Option<()>) -> Option<&'a [u8]> {
f(self.1)?;
Some(self.0)
}
/// Discard the stored value, returning the remaining input.
#[must_use = "this returns the remaining input"]
#[inline]
pub(crate) fn discard_value(self) -> &'a [u8] {
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.
#[inline]
pub(crate) fn filter(self, f: impl FnOnce(&T) -> bool) -> Option<Self> {
f(&self.1).then_some(self)
}
}
impl<'a> ParsedItem<'a, ()> {
/// Discard the unit value, returning the remaining input.
#[must_use = "this returns the remaining input"]
#[inline]
pub(crate) const fn into_inner(self) -> &'a [u8] {
self.0
}
}
+954
View File
@@ -0,0 +1,954 @@
//! A trait that can be used to parse an item from an input.
use core::num::NonZero;
use core::ops::Deref;
use num_conv::prelude::*;
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::error::TryFromParsed;
#[cfg(feature = "alloc")]
use crate::format_description::OwnedFormatItem;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, modifier};
use crate::internal_macros::{bug, try_likely_ok};
use crate::parsing::combinator::{
ExactlyNDigits, Sign, any_digit, ascii_char, ascii_char_ignore_case, one_or_two_digits, sign,
};
use crate::parsing::{Parsed, ParsedItem, component};
use crate::{Date, Month, OffsetDateTime, PrivateMethod, Time, UtcOffset, error};
/// A type that can be parsed.
#[cfg_attr(docsrs, doc(notable_trait))]
#[doc(alias = "Parseable")]
pub trait Parsable: sealed::Sealed {}
impl Parsable for FormatDescriptionV3<'_> {}
impl Parsable for BorrowedFormatItem<'_> {}
impl Parsable for [BorrowedFormatItem<'_>] {}
#[cfg(feature = "alloc")]
impl Parsable for OwnedFormatItem {}
#[cfg(feature = "alloc")]
impl Parsable for [OwnedFormatItem] {}
impl Parsable for Rfc2822 {}
impl Parsable for Rfc3339 {}
impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
impl<T> Parsable for T where T: Deref<Target: Parsable> {}
/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
/// exist in generic bounds.
mod sealed {
use super::*;
use crate::{PlainDateTime, Timestamp, UtcDateTime};
/// Parse the item using a format description and an input.
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
pub trait Sealed {
/// Parse the item into the provided [`Parsed`] struct.
///
/// This method can be used to parse a single component without parsing the full value.
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse>;
/// # **DO NOT USE THIS METHOD**
///
/// This method is for internal use only, has never been part of the public API, and will be
/// removed in a future release. If you are relying on the existence of this method, your
/// code will be broken in the future. The removal of this method will not be considered a
/// breaking change due to the internal nature and the fact that it was never documented as
/// part of the public API.
///
/// You should use the `parse` method on the target type instead. For example, to parse a
/// [`Date`], use [`Date::parse`].
#[deprecated(
since = "0.3.53",
note = "use the `parse` method on the target type; this method has never been part of \
the public API and will be removed in a future release"
)]
#[doc(hidden)]
fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
self.parse_internal(input, None, PrivateMethod)
}
/// Parse the items into a [`Parsed`] struct, using the provided defaults for any components
/// that are not present in the input.
///
/// This method can only be used to parse a complete value of a type. If any characters
/// remain after parsing, an error will be returned.
#[inline]
fn parse_internal(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<Parsed, error::Parse> {
let mut parsed = defaults.unwrap_or_default();
if self
.parse_into(input, &mut parsed, PrivateMethod)?
.is_empty()
{
Ok(parsed)
} else {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
))
}
}
/// Parse a [`Date`] from the format description.
#[inline]
fn parse_date(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<Date, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`Time`] from the format description.
#[inline]
fn parse_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<Time, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`UtcOffset`] from the format description.
#[inline]
fn parse_offset(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<UtcOffset, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`PlainDateTime`] from the format description.
#[inline]
fn parse_plain_date_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<PlainDateTime, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`UtcDateTime`] from the format description.
#[inline]
fn parse_utc_date_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<UtcDateTime, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`OffsetDateTime`] from the format description.
#[inline]
fn parse_offset_date_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<OffsetDateTime, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
/// Parse a [`Timestamp`] from the format description.
#[inline]
fn parse_timestamp(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<Timestamp, error::Parse> {
Ok(self
.parse_internal(input, defaults, PrivateMethod)?
.try_into()?)
}
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for FormatDescriptionV3<'_> {
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_v3_inner(input, &self.inner)?)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for BorrowedFormatItem<'_> {
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_item(input, self)?)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for [BorrowedFormatItem<'_>] {
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_items(input, self)?)
}
}
#[cfg(feature = "alloc")]
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for OwnedFormatItem {
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_item(input, self)?)
}
}
#[cfg(feature = "alloc")]
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for [OwnedFormatItem] {
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_items(input, self)?)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl<T> sealed::Sealed for T
where
T: Deref<Target: sealed::Sealed>,
{
#[inline]
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
self.deref().parse_into(input, parsed, PrivateMethod)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for Rfc2822 {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
use crate::parsing::combinator::rfc::rfc2822::{
cfws, fws, opt_cfws, opt_cfws_colon_opt_cfws, zone_literal,
};
let comma = ascii_char::<b','>;
let input = opt_cfws(input).into_inner();
let weekday = component::parse_weekday_short(
input,
modifier::WeekdayShort {
case_sensitive: false,
},
);
let input = if let Some(item) = weekday {
let input = try_likely_ok!(
item.consume_value(|value| parsed.set_weekday(value))
.ok_or(InvalidComponent("weekday"))
);
let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
opt_cfws(input).into_inner()
} else {
input
};
let input = try_likely_ok!(
one_or_two_digits(input)
.and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
.ok_or(InvalidComponent("day"))
);
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
component::parse_month_short(
input,
modifier::MonthShort {
case_sensitive: false,
},
)
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
.ok_or(InvalidComponent("month"))
);
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
let input = if let Some(ParsedItem(input, year_val)) = ExactlyNDigits::<4>::parse(input) {
if year_val < 1900 {
return Err(error::Parse::ParseFromDescription(InvalidComponent("year")));
}
try_likely_ok!(
parsed
.set_year(year_val.cast_signed().widen())
.ok_or(InvalidComponent("year"))
);
try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner()
} else {
crate::hint::cold_path();
let ParsedItem(input, year) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.map(|item| {
item.map(|year| year.widen::<u32>())
.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
})
.ok_or(InvalidComponent("year"))
);
try_likely_ok!(
parsed
.set_year(year.cast_signed())
.ok_or(InvalidComponent("year"))
);
try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
};
let ParsedItem(input, hour) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
try_likely_ok!(parsed.set_hour_24(hour).ok_or(InvalidComponent("hour")));
let input =
try_likely_ok!(opt_cfws_colon_opt_cfws(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, minute) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
try_likely_ok!(parsed.set_minute(minute).ok_or(InvalidComponent("minute")));
let input = if let Some(input) =
opt_cfws_colon_opt_cfws(input).map(|item| item.into_inner())
{
let ParsedItem(input, second) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
try_likely_ok!(parsed.set_second(second).ok_or(InvalidComponent("second")));
try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
} else {
try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
};
// The RFC explicitly allows leap seconds.
parsed.leap_second_allowed = true;
if let Some(zone_literal) = zone_literal(input) {
crate::hint::cold_path();
let input = try_likely_ok!(
zone_literal
.consume_value(|value| parsed.set_offset_hour(value))
.ok_or(InvalidComponent("offset hour"))
);
try_likely_ok!(
parsed
.set_offset_minute_signed(0)
.ok_or(InvalidComponent("offset minute"))
);
try_likely_ok!(
parsed
.set_offset_second_signed(0)
.ok_or(InvalidComponent("offset second"))
);
return Ok(input);
}
let ParsedItem(input, offset_sign) =
try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| {
item.map(|offset_hour| match offset_sign {
Sign::Negative => -offset_hour.cast_signed(),
Sign::Positive => offset_hour.cast_signed(),
})
.consume_value(|value| parsed.set_offset_hour(value))
})
.ok_or(InvalidComponent("offset hour"))
);
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| {
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_offset_date_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<OffsetDateTime, error::Parse> {
use crate::parsing::combinator::rfc::rfc2822::{
cfws, fws, opt_cfws, opt_cfws_colon_opt_cfws, zone_literal,
};
if let Some(mut defaults) = defaults {
crate::hint::cold_path();
return self
.parse_into(input, &mut defaults, PrivateMethod)
.and_then(|remaining| {
if remaining.is_empty() {
defaults.try_into().map_err(error::Parse::TryFromParsed)
} else {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
))
}
});
}
let comma = ascii_char::<b','>;
let input = opt_cfws(input).into_inner();
let weekday = component::parse_weekday_short(
input,
modifier::WeekdayShort {
case_sensitive: false,
},
);
let input = if let Some(item) = weekday {
let input = item.discard_value();
let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
opt_cfws(input).into_inner()
} else {
input
};
let ParsedItem(input, day) =
try_likely_ok!(one_or_two_digits(input).ok_or(InvalidComponent("day")));
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, month) = try_likely_ok!(
component::parse_month_short(
input,
modifier::MonthShort {
case_sensitive: false,
},
)
.ok_or(InvalidComponent("month"))
);
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
let (input, year) =
if let Some(ParsedItem(input, year_val)) = ExactlyNDigits::<4>::parse(input) {
if year_val < 1900 {
return Err(error::Parse::ParseFromDescription(InvalidComponent("year")));
}
let input = try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner();
(input, year_val)
} else {
crate::hint::cold_path();
let ParsedItem(input, year) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.map(|item| {
item.map(|year| year.widen::<u16>())
.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
})
.ok_or(InvalidComponent("year"))
);
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
(input, year)
};
let ParsedItem(input, hour) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
let input =
try_likely_ok!(opt_cfws_colon_opt_cfws(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, minute) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
let (input, mut second) = if let Some(input) =
opt_cfws_colon_opt_cfws(input).map(|item| item.into_inner())
{
let ParsedItem(input, second) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
(input, second)
} else {
(
try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner(),
0,
)
};
let sign = sign(input);
let (input, offset_hour, offset_minute) = match sign {
None => {
crate::hint::cold_path();
let ParsedItem(input, offset_hour) =
zone_literal(input).ok_or(InvalidComponent("offset hour"))?;
(input, offset_hour, 0)
}
Some(ParsedItem(input, offset_sign)) => {
let ParsedItem(input, offset_hour) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.map(|item| {
item.map(|offset_hour| match offset_sign {
Sign::Negative => -offset_hour.cast_signed(),
Sign::Positive => offset_hour.cast_signed(),
})
})
.ok_or(InvalidComponent("offset hour"))
);
let ParsedItem(input, offset_minute) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
);
(input, offset_hour, offset_minute.cast_signed())
}
};
let input = opt_cfws(input).into_inner();
if !input.is_empty() {
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
let mut nanosecond = 0;
let leap_second_input = if second == 60 {
second = 59;
nanosecond = 999_999_999;
true
} else {
false
};
let dt = try_likely_ok!(
(|| {
let date = try_likely_ok!(Date::from_calendar_date(
year.cast_signed().widen(),
month,
day
));
let time = try_likely_ok!(Time::from_hms_nano(hour, minute, second, nanosecond));
let offset = try_likely_ok!(UtcOffset::from_hms(offset_hour, offset_minute, 0));
Ok(OffsetDateTime::new_in_offset(date, time, offset))
})()
.map_err(TryFromParsed::ComponentRange)
);
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
error::ComponentRange::conditional("second"),
)));
}
Ok(dt)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl sealed::Sealed for Rfc3339 {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
let dash = ascii_char::<b'-'>;
let colon = ascii_char::<b':'>;
let input = try_likely_ok!(
ExactlyNDigits::<4>::parse(input)
.and_then(|item| {
item.consume_value(|value| parsed.set_year(value.cast_signed().widen()))
})
.ok_or(InvalidComponent("year"))
);
let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(
|item| item.flat_map(|value| Month::from_number(NonZero::new(value)?).ok())
)
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
.ok_or(InvalidComponent("month"))
);
let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
.ok_or(InvalidComponent("day"))
);
// RFC3339 allows any separator, not just `T`, not just `space`.
// cf. Section 5.6: Internet Date/Time Format:
// NOTE: ISO 8601 defines date and time separated by "T".
// Applications using this syntax may choose, for the sake of
// readability, to specify a full-date and full-time separated by
// (say) a space character.
// Specifically, rusqlite uses space separators.
let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
.ok_or(InvalidComponent("hour"))
);
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
.ok_or(InvalidComponent("minute"))
);
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| item.consume_value(|value| parsed.set_second(value)))
.ok_or(InvalidComponent("second"))
);
let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) =
try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
.map(|v| (v - b'0').widen::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0').widen::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
try_likely_ok!(
parsed
.set_subsecond(value)
.ok_or(InvalidComponent("subsecond"))
);
input
} else {
input
};
// The RFC explicitly allows leap seconds.
parsed.leap_second_allowed = true;
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
try_likely_ok!(
parsed
.set_offset_hour(0)
.ok_or(InvalidComponent("offset hour"))
);
try_likely_ok!(
parsed
.set_offset_minute_signed(0)
.ok_or(InvalidComponent("offset minute"))
);
try_likely_ok!(
parsed
.set_offset_second_signed(0)
.ok_or(InvalidComponent("offset second"))
);
return Ok(input);
}
let ParsedItem(input, offset_sign) =
try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| {
item.filter(|&offset_hour| offset_hour <= 23)?
.map(|offset_hour| match offset_sign {
Sign::Negative => -offset_hour.cast_signed(),
Sign::Positive => offset_hour.cast_signed(),
})
.consume_value(|value| parsed.set_offset_hour(value))
})
.ok_or(InvalidComponent("offset hour"))
);
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let input = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|item| {
item.map(|offset_minute| match offset_sign {
Sign::Negative => -offset_minute.cast_signed(),
Sign::Positive => offset_minute.cast_signed(),
})
.consume_value(|value| parsed.set_offset_minute_signed(value))
})
.ok_or(InvalidComponent("offset minute"))
);
Ok(input)
}
fn parse_offset_date_time(
&self,
input: &[u8],
defaults: Option<Parsed>,
_: PrivateMethod,
) -> Result<OffsetDateTime, error::Parse> {
if let Some(mut defaults) = defaults {
crate::hint::cold_path();
return self
.parse_into(input, &mut defaults, PrivateMethod)
.and_then(|remaining| {
if remaining.is_empty() {
defaults.try_into().map_err(error::Parse::TryFromParsed)
} else {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
))
}
});
}
let dash = ascii_char::<b'-'>;
let colon = ascii_char::<b':'>;
let ParsedItem(input, year) =
try_likely_ok!(ExactlyNDigits::<4>::parse(input).ok_or(InvalidComponent("year")));
let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, month) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|parsed| parsed.flat_map(NonZero::new))
.ok_or(InvalidComponent("month"))
);
let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, day) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("day")));
// RFC3339 allows any separator, not just `T`, not just `space`.
// cf. Section 5.6: Internet Date/Time Format:
// NOTE: ISO 8601 defines date and time separated by "T".
// Applications using this syntax may choose, for the sake of
// readability, to specify a full-date and full-time separated by
// (say) a space character.
// Specifically, rusqlite uses space separators.
let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
let ParsedItem(input, hour) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, minute) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, mut second) =
try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
let ParsedItem(input, mut nanosecond) =
if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) =
try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
.map(|v| (v - b'0').widen::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0').widen::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
ParsedItem(input, value)
} else {
ParsedItem(input, 0)
};
let ParsedItem(input, offset) = {
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
ParsedItem(input, UtcOffset::UTC)
} else {
let ParsedItem(input, offset_sign) =
try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
let ParsedItem(input, offset_hour) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input)
.and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
.ok_or(InvalidComponent("offset hour"))
);
let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
let ParsedItem(input, offset_minute) = try_likely_ok!(
ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
);
try_likely_ok!(
match offset_sign {
Sign::Negative => UtcOffset::from_hms(
-offset_hour.cast_signed(),
-offset_minute.cast_signed(),
0,
),
Sign::Positive => UtcOffset::from_hms(
offset_hour.cast_signed(),
offset_minute.cast_signed(),
0,
),
}
.map(|offset| ParsedItem(input, offset))
.map_err(TryFromParsed::ComponentRange)
)
}
};
if !input.is_empty() {
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
// The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
// the preceding nanosecond. However, leap seconds can only occur as the last second of the
// month UTC.
let leap_second_input = if second == 60 {
second = 59;
nanosecond = 999_999_999;
true
} else {
false
};
let date = try_likely_ok!(
Month::from_number(month)
.and_then(|month| Date::from_calendar_date(year.cast_signed().widen(), month, day))
.map_err(TryFromParsed::ComponentRange)
);
let time = try_likely_ok!(
Time::from_hms_nano(hour, minute, second, nanosecond)
.map_err(TryFromParsed::ComponentRange)
);
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(
error::ComponentRange::conditional("second"),
)));
}
Ok(dt)
}
}
#[expect(
private_interfaces,
reason = "not intended to be used by downstream users"
)]
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
#[inline]
fn parse_into<'a>(
&self,
mut input: &'a [u8],
parsed: &mut Parsed,
_: PrivateMethod,
) -> Result<&'a [u8], error::Parse> {
use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
let mut extended_kind = ExtendedKind::Unknown;
let mut date_is_present = false;
let mut time_is_present = false;
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;
date_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
Ok(new_input) => {
input = new_input;
time_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
// If a date and offset are present, a time must be as well.
if !date_is_present || time_is_present {
match Self::parse_offset(parsed, &mut extended_kind)(input) {
Ok(new_input) => {
input = new_input;
offset_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
}
if !date_is_present && !time_is_present && !offset_is_present {
match first_error {
Some(err) => return Err(err),
None => bug!("an error should be present if no components were parsed"),
}
}
Ok(input)
}
}
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
//! Extension traits for things either not implemented or not yet stable in the MSRV.
/// Marker trait for integer types.
pub(crate) trait Integer: Sized {
/// The maximum number of digits that this type can have.
const MAX_NUM_DIGITS: u8;
/// The zero value for this type.
const ZERO: Self;
/// Push a digit onto the end of this integer, assuming no overflow.
///
/// This is equivalent to `self * 10 + digit`.
fn push_digit(self, digit: u8) -> Self;
/// Push a digit onto the end of this integer, returning `None` on overflow.
///
/// This is equivalent to `self.checked_mul(10)?.checked_add(digit)`.
fn checked_push_digit(self, digit: u8) -> Option<Self>;
}
/// Parse the given types from bytes.
macro_rules! impl_parse_bytes {
($($t:ty)*) => ($(
impl Integer for $t {
const MAX_NUM_DIGITS: u8 = match Self::MAX.checked_ilog10() {
Some(digits) => digits as u8 + 1,
None => 1,
};
const ZERO: Self = 0;
#[allow(trivial_numeric_casts, reason = "macro-generated code")]
#[inline]
fn push_digit(self, digit: u8) -> Self {
self * 10 + digit as Self
}
#[allow(trivial_numeric_casts, reason = "macro-generated code")]
#[inline]
fn checked_push_digit(self, digit: u8) -> Option<Self> {
self.checked_mul(10)?.checked_add(digit as Self)
}
}
)*)
}
impl_parse_bytes! { u8 u16 u32 u128 }
File diff suppressed because it is too large Load Diff
+265
View File
@@ -0,0 +1,265 @@
//! Implementations of the [`quickcheck::Arbitrary`](quickcheck::Arbitrary) trait.
//!
//! This enables users to write tests such as this, and have test values provided automatically:
//!
//! ```ignore
//! # #![expect(dead_code)]
//! use quickcheck::quickcheck;
//! use time::Date;
//!
//! struct DateRange {
//! from: Date,
//! to: Date,
//! }
//!
//! impl DateRange {
//! fn new(from: Date, to: Date) -> Result<Self, ()> {
//! Ok(DateRange { from, to })
//! }
//! }
//!
//! quickcheck! {
//! fn date_range_is_well_defined(from: Date, to: Date) -> bool {
//! let r = DateRange::new(from, to);
//! if from <= to {
//! r.is_ok()
//! } else {
//! r.is_err()
//! }
//! }
//! }
//! ```
//!
//! An implementation for `Instant` is intentionally omitted since its values are only meaningful in
//! relation to a [`SignedDuration`], and obtaining an `Instant` from a [`SignedDuration`] is very
//! simple anyway.
use alloc::boxed::Box;
use quickcheck::{Arbitrary, Gen, empty_shrinker, single_shrinker};
use crate::{
Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, Timestamp, UtcDateTime,
UtcOffset, Weekday,
};
/// Obtain an arbitrary value between the minimum and maximum inclusive.
macro_rules! arbitrary_between {
($type:ty; $gen:expr, $min:expr, $max:expr) => {{
let min = $min;
let max = $max;
let range = max - min;
<$type>::arbitrary($gen).rem_euclid(range + 1) + min
}};
}
impl Arbitrary for Date {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
// Safety: The Julian day number is in range.
unsafe {
Self::from_julian_day_unchecked(arbitrary_between!(
i32;
g,
Self::MIN.to_julian_day(),
Self::MAX.to_julian_day()
))
}
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.to_ordinal_date()
.shrink()
.flat_map(|(year, ordinal)| Self::from_ordinal_date(year, ordinal)),
)
}
}
impl Arbitrary for SignedDuration {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::new_ranged(<_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.subsec_nanoseconds_ranged(), self.whole_seconds())
.shrink()
.map(|(mut nanoseconds, seconds)| {
// Coerce the sign if necessary.
if (seconds > 0 && nanoseconds.get() < 0)
|| (seconds < 0 && nanoseconds.get() > 0)
{
nanoseconds = nanoseconds.neg();
}
Self::new_ranged_unchecked(seconds, nanoseconds)
}),
)
}
}
impl Arbitrary for Time {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::from_hms_nanos_ranged(
<_>::arbitrary(g),
<_>::arbitrary(g),
<_>::arbitrary(g),
<_>::arbitrary(g),
)
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.as_hms_nano_ranged()
.shrink()
.map(|(hour, minute, second, nanosecond)| {
Self::from_hms_nanos_ranged(hour, minute, second, nanosecond)
}),
)
}
}
impl Arbitrary for PlainDateTime {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::new(<_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.date(), self.time())
.shrink()
.map(|(date, time)| Self::new(date, time)),
)
}
}
impl Arbitrary for UtcOffset {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::from_hms_ranged(<_>::arbitrary(g), <_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.as_hms_ranged()
.shrink()
.map(|(hours, minutes, seconds)| Self::from_hms_ranged(hours, minutes, seconds)),
)
}
}
impl Arbitrary for OffsetDateTime {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::new_in_offset(<_>::arbitrary(g), <_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.date(), self.time(), self.offset())
.shrink()
.map(|(date, time, offset)| Self::new_in_offset(date, time, offset)),
)
}
}
impl Arbitrary for UtcDateTime {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::new(<_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
(self.date(), self.time())
.shrink()
.map(|(date, time)| Self::new(date, time)),
)
}
}
impl Arbitrary for Timestamp {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
Self::new_ranged(<_>::arbitrary(g), <_>::arbitrary(g))
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.as_parts_ranged()
.shrink()
.map(|(seconds, nanoseconds)| Self::new_ranged(seconds, nanoseconds)),
)
}
}
impl Arbitrary for Weekday {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
use Weekday::*;
match arbitrary_between!(u8; g, 0, 6) {
0 => Monday,
1 => Tuesday,
2 => Wednesday,
3 => Thursday,
4 => Friday,
5 => Saturday,
val => {
debug_assert!(val == 6);
Sunday
}
}
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
match self {
Self::Monday => empty_shrinker(),
_ => single_shrinker(self.previous()),
}
}
}
impl Arbitrary for Month {
#[inline]
fn arbitrary(g: &mut Gen) -> Self {
use Month::*;
match arbitrary_between!(u8; g, 1, 12) {
1 => January,
2 => February,
3 => March,
4 => April,
5 => May,
6 => June,
7 => July,
8 => August,
9 => September,
10 => October,
11 => November,
val => {
debug_assert!(val == 12);
December
}
}
}
#[inline]
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
match self {
Self::January => empty_shrinker(),
_ => single_shrinker(self.previous()),
}
}
}
+145
View File
@@ -0,0 +1,145 @@
//! Implementation of [`Distribution`] for various structs.
use rand010::distr::{Distribution, StandardUniform};
use rand010::{Rng, RngExt as _};
use crate::{
Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, Timestamp, UtcDateTime,
UtcOffset, Weekday,
};
impl Distribution<Time> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Time
where
R: Rng + ?Sized,
{
Time::from_hms_nanos_ranged(rng.random(), rng.random(), rng.random(), rng.random())
}
}
impl Distribution<Date> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Date
where
R: Rng + ?Sized,
{
// Safety: The Julian day number is in range.
unsafe {
Date::from_julian_day_unchecked(
rng.random_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()),
)
}
}
}
impl Distribution<UtcOffset> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcOffset
where
R: Rng + ?Sized,
{
UtcOffset::from_hms_ranged(rng.random(), rng.random(), rng.random())
}
}
impl Distribution<PlainDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> PlainDateTime
where
R: Rng + ?Sized,
{
PlainDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<UtcDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcDateTime
where
R: Rng + ?Sized,
{
UtcDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<OffsetDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> OffsetDateTime
where
R: Rng + ?Sized,
{
let date_time: PlainDateTime = Self.sample(rng);
date_time.assume_offset(Self.sample(rng))
}
}
impl Distribution<Timestamp> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Timestamp
where
R: Rng + ?Sized,
{
Timestamp::new_ranged(rng.random(), rng.random())
}
}
impl Distribution<SignedDuration> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> SignedDuration
where
R: Rng + ?Sized,
{
SignedDuration::new_ranged(rng.random(), rng.random())
}
}
impl Distribution<Weekday> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Weekday
where
R: Rng + ?Sized,
{
use Weekday::*;
match rng.random_range(0u8..7) {
0 => Monday,
1 => Tuesday,
2 => Wednesday,
3 => Thursday,
4 => Friday,
5 => Saturday,
val => {
debug_assert!(val == 6);
Sunday
}
}
}
}
impl Distribution<Month> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Month
where
R: Rng + ?Sized,
{
use Month::*;
match rng.random_range(1u8..=12) {
1 => January,
2 => February,
3 => March,
4 => April,
5 => May,
6 => June,
7 => July,
8 => August,
9 => September,
10 => October,
11 => November,
val => {
debug_assert!(val == 12);
December
}
}
}
}
+142
View File
@@ -0,0 +1,142 @@
//! Implementation of [`Distribution`] for various structs.
use rand08::Rng;
use rand08::distributions::{Distribution, Standard};
use crate::{
Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, Timestamp, UtcDateTime,
UtcOffset, Weekday,
};
impl Distribution<Time> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Time
where
R: Rng + ?Sized,
{
Time::from_hms_nanos_ranged(rng.r#gen(), rng.r#gen(), rng.r#gen(), rng.r#gen())
}
}
impl Distribution<Date> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Date
where
R: Rng + ?Sized,
{
// Safety: The Julian day number is in range.
unsafe {
Date::from_julian_day_unchecked(
rng.gen_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()),
)
}
}
}
impl Distribution<UtcOffset> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcOffset
where
R: Rng + ?Sized,
{
UtcOffset::from_hms_ranged(rng.r#gen(), rng.r#gen(), rng.r#gen())
}
}
impl Distribution<PlainDateTime> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> PlainDateTime
where
R: Rng + ?Sized,
{
PlainDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<UtcDateTime> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcDateTime
where
R: Rng + ?Sized,
{
UtcDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<OffsetDateTime> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> OffsetDateTime
where
R: Rng + ?Sized,
{
let date_time: PlainDateTime = Self.sample(rng);
date_time.assume_offset(Self.sample(rng))
}
}
impl Distribution<Timestamp> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Timestamp {
Timestamp::new_ranged(rng.r#gen(), rng.r#gen())
}
}
impl Distribution<SignedDuration> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> SignedDuration
where
R: Rng + ?Sized,
{
SignedDuration::new_ranged(rng.r#gen(), rng.r#gen())
}
}
impl Distribution<Weekday> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Weekday
where
R: Rng + ?Sized,
{
use Weekday::*;
match rng.gen_range(0u8..7) {
0 => Monday,
1 => Tuesday,
2 => Wednesday,
3 => Thursday,
4 => Friday,
5 => Saturday,
val => {
debug_assert!(val == 6);
Sunday
}
}
}
}
impl Distribution<Month> for Standard {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Month
where
R: Rng + ?Sized,
{
use Month::*;
match rng.gen_range(1u8..=12) {
1 => January,
2 => February,
3 => March,
4 => April,
5 => May,
6 => June,
7 => July,
8 => August,
9 => September,
10 => October,
11 => November,
val => {
debug_assert!(val == 12);
December
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
//! Implementation of [`Distribution`] for various structs.
use rand09::Rng;
use rand09::distr::{Distribution, StandardUniform};
use crate::{
Date, Month, OffsetDateTime, PlainDateTime, SignedDuration, Time, Timestamp, UtcDateTime,
UtcOffset, Weekday,
};
impl Distribution<Time> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Time
where
R: Rng + ?Sized,
{
Time::from_hms_nanos_ranged(rng.random(), rng.random(), rng.random(), rng.random())
}
}
impl Distribution<Date> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Date
where
R: Rng + ?Sized,
{
// Safety: The Julian day number is in range.
unsafe {
Date::from_julian_day_unchecked(
rng.random_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()),
)
}
}
}
impl Distribution<UtcOffset> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcOffset
where
R: Rng + ?Sized,
{
UtcOffset::from_hms_ranged(rng.random(), rng.random(), rng.random())
}
}
impl Distribution<PlainDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> PlainDateTime
where
R: Rng + ?Sized,
{
PlainDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<UtcDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> UtcDateTime
where
R: Rng + ?Sized,
{
UtcDateTime::new(Self.sample(rng), Self.sample(rng))
}
}
impl Distribution<OffsetDateTime> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> OffsetDateTime
where
R: Rng + ?Sized,
{
let date_time: PlainDateTime = Self.sample(rng);
date_time.assume_offset(Self.sample(rng))
}
}
impl Distribution<Timestamp> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Timestamp
where
R: Rng + ?Sized,
{
Timestamp::new_ranged(rng.random(), rng.random())
}
}
impl Distribution<SignedDuration> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> SignedDuration
where
R: Rng + ?Sized,
{
SignedDuration::new_ranged(rng.random(), rng.random())
}
}
impl Distribution<Weekday> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Weekday
where
R: Rng + ?Sized,
{
use Weekday::*;
match rng.random_range(0u8..7) {
0 => Monday,
1 => Tuesday,
2 => Wednesday,
3 => Thursday,
4 => Friday,
5 => Saturday,
val => {
debug_assert!(val == 6);
Sunday
}
}
}
}
impl Distribution<Month> for StandardUniform {
#[inline]
fn sample<R>(&self, rng: &mut R) -> Month
where
R: Rng + ?Sized,
{
use Month::*;
match rng.random_range(1u8..=12) {
1 => January,
2 => February,
3 => March,
4 => April,
5 => May,
6 => June,
7 => July,
8 => August,
9 => September,
10 => October,
11 => November,
val => {
debug_assert!(val == 12);
December
}
}
}
}

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