summaryrefslogtreecommitdiffstats
path: root/third_party/rust/time
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/time
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--third_party/rust/time-0.1.45/.cargo-checksum.json1
-rw-r--r--third_party/rust/time-0.1.45/Cargo.toml60
-rw-r--r--third_party/rust/time-0.1.45/LICENSE-APACHE201
-rw-r--r--third_party/rust/time-0.1.45/LICENSE-MIT25
-rw-r--r--third_party/rust/time-0.1.45/README.md31
-rw-r--r--third_party/rust/time-0.1.45/src/display.rs260
-rw-r--r--third_party/rust/time-0.1.45/src/duration.rs655
-rw-r--r--third_party/rust/time-0.1.45/src/lib.rs1283
-rw-r--r--third_party/rust/time-0.1.45/src/parse.rs395
-rw-r--r--third_party/rust/time-0.1.45/src/sys.rs996
-rw-r--r--third_party/rust/time-core/.cargo-checksum.json1
-rw-r--r--third_party/rust/time-core/Cargo.toml32
-rw-r--r--third_party/rust/time-core/LICENSE-Apache202
-rw-r--r--third_party/rust/time-core/LICENSE-MIT19
-rw-r--r--third_party/rust/time-core/src/lib.rs52
-rw-r--r--third_party/rust/time-core/src/util.rs52
-rw-r--r--third_party/rust/time-macros/.cargo-checksum.json1
-rw-r--r--third_party/rust/time-macros/Cargo.toml45
-rw-r--r--third_party/rust/time-macros/LICENSE-Apache202
-rw-r--r--third_party/rust/time-macros/LICENSE-MIT19
-rw-r--r--third_party/rust/time-macros/src/date.rs137
-rw-r--r--third_party/rust/time-macros/src/datetime.rs57
-rw-r--r--third_party/rust/time-macros/src/error.rs136
-rw-r--r--third_party/rust/time-macros/src/format_description/component.rs168
-rw-r--r--third_party/rust/time-macros/src/format_description/error.rs29
-rw-r--r--third_party/rust/time-macros/src/format_description/mod.rs40
-rw-r--r--third_party/rust/time-macros/src/format_description/modifier.rs417
-rw-r--r--third_party/rust/time-macros/src/format_description/parse.rs84
-rw-r--r--third_party/rust/time-macros/src/helpers/mod.rs129
-rw-r--r--third_party/rust/time-macros/src/helpers/string.rs188
-rw-r--r--third_party/rust/time-macros/src/lib.rs167
-rw-r--r--third_party/rust/time-macros/src/offset.rs95
-rw-r--r--third_party/rust/time-macros/src/quote.rs134
-rw-r--r--third_party/rust/time-macros/src/serde_format_description.rs163
-rw-r--r--third_party/rust/time-macros/src/time.rs118
-rw-r--r--third_party/rust/time-macros/src/to_tokens.rs68
-rw-r--r--third_party/rust/time/.cargo-checksum.json1
-rw-r--r--third_party/rust/time/Cargo.toml166
-rw-r--r--third_party/rust/time/LICENSE-Apache202
-rw-r--r--third_party/rust/time/LICENSE-MIT19
-rw-r--r--third_party/rust/time/README.md42
-rw-r--r--third_party/rust/time/src/date.rs1050
-rw-r--r--third_party/rust/time/src/duration.rs1139
-rw-r--r--third_party/rust/time/src/error/component_range.rs92
-rw-r--r--third_party/rust/time/src/error/conversion_range.rs36
-rw-r--r--third_party/rust/time/src/error/different_variant.rs34
-rw-r--r--third_party/rust/time/src/error/format.rs92
-rw-r--r--third_party/rust/time/src/error/indeterminate_offset.rs35
-rw-r--r--third_party/rust/time/src/error/invalid_format_description.rs80
-rw-r--r--third_party/rust/time/src/error/invalid_variant.rs34
-rw-r--r--third_party/rust/time/src/error/mod.rs112
-rw-r--r--third_party/rust/time/src/error/parse.rs97
-rw-r--r--third_party/rust/time/src/error/parse_from_description.rs47
-rw-r--r--third_party/rust/time/src/error/try_from_parsed.rs71
-rw-r--r--third_party/rust/time/src/ext.rs279
-rw-r--r--third_party/rust/time/src/format_description/borrowed_format_item.rs106
-rw-r--r--third_party/rust/time/src/format_description/component.rs37
-rw-r--r--third_party/rust/time/src/format_description/mod.rs34
-rw-r--r--third_party/rust/time/src/format_description/modifier.rs355
-rw-r--r--third_party/rust/time/src/format_description/owned_format_item.rs162
-rw-r--r--third_party/rust/time/src/format_description/parse/ast.rs278
-rw-r--r--third_party/rust/time/src/format_description/parse/format_item.rs386
-rw-r--r--third_party/rust/time/src/format_description/parse/lexer.rs159
-rw-r--r--third_party/rust/time/src/format_description/parse/mod.rs193
-rw-r--r--third_party/rust/time/src/format_description/well_known/iso8601.rs233
-rw-r--r--third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs249
-rw-r--r--third_party/rust/time/src/format_description/well_known/rfc2822.rs30
-rw-r--r--third_party/rust/time/src/format_description/well_known/rfc3339.rs30
-rw-r--r--third_party/rust/time/src/formatting/formattable.rs304
-rw-r--r--third_party/rust/time/src/formatting/iso8601.rs139
-rw-r--r--third_party/rust/time/src/formatting/mod.rs506
-rw-r--r--third_party/rust/time/src/instant.rs262
-rw-r--r--third_party/rust/time/src/lib.rs357
-rw-r--r--third_party/rust/time/src/macros.rs132
-rw-r--r--third_party/rust/time/src/month.rs164
-rw-r--r--third_party/rust/time/src/offset_date_time.rs1398
-rw-r--r--third_party/rust/time/src/parsing/combinator/mod.rs192
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs173
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/mod.rs10
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs13
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs115
-rw-r--r--third_party/rust/time/src/parsing/component.rs296
-rw-r--r--third_party/rust/time/src/parsing/iso8601.rs308
-rw-r--r--third_party/rust/time/src/parsing/mod.rs50
-rw-r--r--third_party/rust/time/src/parsing/parsable.rs754
-rw-r--r--third_party/rust/time/src/parsing/parsed.rs759
-rw-r--r--third_party/rust/time/src/parsing/shim.rs50
-rw-r--r--third_party/rust/time/src/primitive_date_time.rs937
-rw-r--r--third_party/rust/time/src/quickcheck.rs220
-rw-r--r--third_party/rust/time/src/rand.rs99
-rw-r--r--third_party/rust/time/src/serde/iso8601.rs77
-rw-r--r--third_party/rust/time/src/serde/mod.rs375
-rw-r--r--third_party/rust/time/src/serde/rfc2822.rs72
-rw-r--r--third_party/rust/time/src/serde/rfc3339.rs72
-rw-r--r--third_party/rust/time/src/serde/timestamp.rs60
-rw-r--r--third_party/rust/time/src/serde/visitor.rs316
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/imp.rs8
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/mod.rs23
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/unix.rs169
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/wasm_js.rs12
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/windows.rs114
-rw-r--r--third_party/rust/time/src/sys/mod.rs9
-rw-r--r--third_party/rust/time/src/tests.rs113
-rw-r--r--third_party/rust/time/src/time.rs761
-rw-r--r--third_party/rust/time/src/utc_offset.rs346
-rw-r--r--third_party/rust/time/src/util.rs31
-rw-r--r--third_party/rust/time/src/weekday.rs148
107 files changed, 22486 insertions, 0 deletions
diff --git a/third_party/rust/time-0.1.45/.cargo-checksum.json b/third_party/rust/time-0.1.45/.cargo-checksum.json
new file mode 100644
index 0000000000..853f851921
--- /dev/null
+++ b/third_party/rust/time-0.1.45/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"27cdb65721e671b4a942a99b69f6b8679e7ec258cd8c2e5edec8eb50a91b3b58","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb","README.md":"35b591c7481ec3ae59210fa4f9b7cecb1b16e632bfd6193b032239e74f9bfdb8","src/display.rs":"52d16abaa37b3ab577747c7d9d2ed6ded1b126458e980dc3e1a571fa6e1f9fda","src/duration.rs":"c706d392bdb7f65b23fcc20189a9a77c50b765b9c548e247238424ed6fb56a46","src/lib.rs":"86f27545d9dd5fcef7797da46ec101f22226760c1b8ac49a171d0b2fc374f478","src/parse.rs":"65bd9142d8c15eb54a8d4db6e2c48bf1adbcc875953141c17e07ba58f356a027","src/sys.rs":"f9208c822fec9dabdd9c9e70aae8822dd13bac7217e5057c82a398d24fcadc7a"},"package":"1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"} \ No newline at end of file
diff --git a/third_party/rust/time-0.1.45/Cargo.toml b/third_party/rust/time-0.1.45/Cargo.toml
new file mode 100644
index 0000000000..4f6912ef9d
--- /dev/null
+++ b/third_party/rust/time-0.1.45/Cargo.toml
@@ -0,0 +1,60 @@
+# 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]
+name = "time"
+version = "0.1.45"
+authors = ["The Rust Project Developers"]
+exclude = [
+ ".github",
+ "benches",
+]
+description = """
+Utilities for working with time-related functions in Rust.
+"""
+homepage = "https://github.com/time-rs/time"
+documentation = "https://docs.rs/time/~0.1"
+readme = "README.md"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/time-rs/time"
+
+[dependencies.libc]
+version = "0.2.69"
+
+[dependencies.rustc-serialize]
+version = "0.3"
+optional = true
+
+[dev-dependencies.log]
+version = "0.4"
+
+[dev-dependencies.winapi]
+version = "0.3.0"
+features = [
+ "std",
+ "processthreadsapi",
+ "winbase",
+]
+
+[target."cfg(target_os = \"wasi\")".dependencies.wasi]
+version = "=0.10.0"
+
+[target."cfg(windows)".dependencies.winapi]
+version = "0.3.0"
+features = [
+ "std",
+ "minwinbase",
+ "minwindef",
+ "ntdef",
+ "profileapi",
+ "sysinfoapi",
+ "timezoneapi",
+]
diff --git a/third_party/rust/time-0.1.45/LICENSE-APACHE b/third_party/rust/time-0.1.45/LICENSE-APACHE
new file mode 100644
index 0000000000..16fe87b06e
--- /dev/null
+++ b/third_party/rust/time-0.1.45/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/rust/time-0.1.45/LICENSE-MIT b/third_party/rust/time-0.1.45/LICENSE-MIT
new file mode 100644
index 0000000000..39d4bdb5ac
--- /dev/null
+++ b/third_party/rust/time-0.1.45/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 The Rust Project Developers
+
+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.
diff --git a/third_party/rust/time-0.1.45/README.md b/third_party/rust/time-0.1.45/README.md
new file mode 100644
index 0000000000..f427eb3ea4
--- /dev/null
+++ b/third_party/rust/time-0.1.45/README.md
@@ -0,0 +1,31 @@
+time
+====
+
+Utilities for working with time-related functions in Rust
+
+[![build status](https://github.com/time-rs/time/workflows/Build/badge.svg?branch=v0.1)](https://github.com/time-rs/time/actions?query=branch%3Av0.1)
+[![Documentation](https://docs.rs/time/badge.svg?version=0.1)](https://docs.rs/time/~0.1)
+![rustc 1.21.0](https://img.shields.io/badge/rustc-1.21.0-blue)
+
+## time v0.1.x is Deprecated
+
+The 0.1.x series of this library is deprecated and in maintenance mode. No new
+features will be added. Active development now occurs in the 0.2.x series.
+
+If you need additional functionality that this crate does not provide, check
+out the [`chrono`](https://github.com/chronotope/chrono) crate.
+
+## Usage
+
+Put this in your `Cargo.toml`:
+
+```toml
+[dependencies]
+time = "0.1"
+```
+
+And this in your crate root:
+
+```rust
+extern crate time;
+```
diff --git a/third_party/rust/time-0.1.45/src/display.rs b/third_party/rust/time-0.1.45/src/display.rs
new file mode 100644
index 0000000000..705b43078a
--- /dev/null
+++ b/third_party/rust/time-0.1.45/src/display.rs
@@ -0,0 +1,260 @@
+use std::fmt::{self, Write};
+
+use super::{TmFmt, Tm, Fmt};
+
+impl<'a> fmt::Display for TmFmt<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self.format {
+ Fmt::Str(ref s) => {
+ let mut chars = s.chars();
+ while let Some(ch) = chars.next() {
+ if ch == '%' {
+ // we've already validated that % always precedes
+ // another char
+ parse_type(fmt, chars.next().unwrap(), self.tm)?;
+ } else {
+ fmt.write_char(ch)?;
+ }
+ }
+
+ Ok(())
+ }
+ Fmt::Ctime => self.tm.to_local().asctime().fmt(fmt),
+ Fmt::Rfc3339 => {
+ if self.tm.tm_utcoff == 0 {
+ TmFmt {
+ tm: self.tm,
+ format: Fmt::Str("%Y-%m-%dT%H:%M:%SZ"),
+ }.fmt(fmt)
+ } else {
+ let s = TmFmt {
+ tm: self.tm,
+ format: Fmt::Str("%Y-%m-%dT%H:%M:%S"),
+ };
+ let sign = if self.tm.tm_utcoff > 0 { '+' } else { '-' };
+ let mut m = abs(self.tm.tm_utcoff) / 60;
+ let h = m / 60;
+ m -= h * 60;
+ write!(fmt, "{}{}{:02}:{:02}", s, sign, h, m)
+ }
+ }
+ }
+ }
+}
+
+fn is_leap_year(year: i32) -> bool {
+ (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
+}
+
+fn days_in_year(year: i32) -> i32 {
+ if is_leap_year(year) { 366 }
+ else { 365 }
+}
+
+fn iso_week_days(yday: i32, wday: i32) -> i32 {
+ /* The number of days from the first day of the first ISO week of this
+ * year to the year day YDAY with week day WDAY.
+ * ISO weeks start on Monday. The first ISO week has the year's first
+ * Thursday.
+ * YDAY may be as small as yday_minimum.
+ */
+ let iso_week_start_wday: i32 = 1; /* Monday */
+ let iso_week1_wday: i32 = 4; /* Thursday */
+ let yday_minimum: i32 = 366;
+ /* Add enough to the first operand of % to make it nonnegative. */
+ let big_enough_multiple_of_7: i32 = (yday_minimum / 7 + 2) * 7;
+
+ yday - (yday - wday + iso_week1_wday + big_enough_multiple_of_7) % 7
+ + iso_week1_wday - iso_week_start_wday
+}
+
+fn iso_week(fmt: &mut fmt::Formatter, ch:char, tm: &Tm) -> fmt::Result {
+ let mut year = tm.tm_year + 1900;
+ let mut days = iso_week_days(tm.tm_yday, tm.tm_wday);
+
+ if days < 0 {
+ /* This ISO week belongs to the previous year. */
+ year -= 1;
+ days = iso_week_days(tm.tm_yday + (days_in_year(year)), tm.tm_wday);
+ } else {
+ let d = iso_week_days(tm.tm_yday - (days_in_year(year)),
+ tm.tm_wday);
+ if 0 <= d {
+ /* This ISO week belongs to the next year. */
+ year += 1;
+ days = d;
+ }
+ }
+
+ match ch {
+ 'G' => write!(fmt, "{}", year),
+ 'g' => write!(fmt, "{:02}", (year % 100 + 100) % 100),
+ 'V' => write!(fmt, "{:02}", days / 7 + 1),
+ _ => Ok(())
+ }
+}
+
+fn parse_type(fmt: &mut fmt::Formatter, ch: char, tm: &Tm) -> fmt::Result {
+ match ch {
+ 'A' => fmt.write_str(match tm.tm_wday {
+ 0 => "Sunday",
+ 1 => "Monday",
+ 2 => "Tuesday",
+ 3 => "Wednesday",
+ 4 => "Thursday",
+ 5 => "Friday",
+ 6 => "Saturday",
+ _ => unreachable!(),
+ }),
+ 'a' => fmt.write_str(match tm.tm_wday {
+ 0 => "Sun",
+ 1 => "Mon",
+ 2 => "Tue",
+ 3 => "Wed",
+ 4 => "Thu",
+ 5 => "Fri",
+ 6 => "Sat",
+ _ => unreachable!(),
+ }),
+ 'B' => fmt.write_str(match tm.tm_mon {
+ 0 => "January",
+ 1 => "February",
+ 2 => "March",
+ 3 => "April",
+ 4 => "May",
+ 5 => "June",
+ 6 => "July",
+ 7 => "August",
+ 8 => "September",
+ 9 => "October",
+ 10 => "November",
+ 11 => "December",
+ _ => unreachable!(),
+ }),
+ 'b' | 'h' => fmt.write_str(match tm.tm_mon {
+ 0 => "Jan",
+ 1 => "Feb",
+ 2 => "Mar",
+ 3 => "Apr",
+ 4 => "May",
+ 5 => "Jun",
+ 6 => "Jul",
+ 7 => "Aug",
+ 8 => "Sep",
+ 9 => "Oct",
+ 10 => "Nov",
+ 11 => "Dec",
+ _ => unreachable!(),
+ }),
+ 'C' => write!(fmt, "{:02}", (tm.tm_year + 1900) / 100),
+ 'c' => {
+ parse_type(fmt, 'a', tm)?;
+ fmt.write_str(" ")?;
+ parse_type(fmt, 'b', tm)?;
+ fmt.write_str(" ")?;
+ parse_type(fmt, 'e', tm)?;
+ fmt.write_str(" ")?;
+ parse_type(fmt, 'T', tm)?;
+ fmt.write_str(" ")?;
+ parse_type(fmt, 'Y', tm)
+ }
+ 'D' | 'x' => {
+ parse_type(fmt, 'm', tm)?;
+ fmt.write_str("/")?;
+ parse_type(fmt, 'd', tm)?;
+ fmt.write_str("/")?;
+ parse_type(fmt, 'y', tm)
+ }
+ 'd' => write!(fmt, "{:02}", tm.tm_mday),
+ 'e' => write!(fmt, "{:2}", tm.tm_mday),
+ 'f' => write!(fmt, "{:09}", tm.tm_nsec),
+ 'F' => {
+ parse_type(fmt, 'Y', tm)?;
+ fmt.write_str("-")?;
+ parse_type(fmt, 'm', tm)?;
+ fmt.write_str("-")?;
+ parse_type(fmt, 'd', tm)
+ }
+ 'G' => iso_week(fmt, 'G', tm),
+ 'g' => iso_week(fmt, 'g', tm),
+ 'H' => write!(fmt, "{:02}", tm.tm_hour),
+ 'I' => {
+ let mut h = tm.tm_hour;
+ if h == 0 { h = 12 }
+ if h > 12 { h -= 12 }
+ write!(fmt, "{:02}", h)
+ }
+ 'j' => write!(fmt, "{:03}", tm.tm_yday + 1),
+ 'k' => write!(fmt, "{:2}", tm.tm_hour),
+ 'l' => {
+ let mut h = tm.tm_hour;
+ if h == 0 { h = 12 }
+ if h > 12 { h -= 12 }
+ write!(fmt, "{:2}", h)
+ }
+ 'M' => write!(fmt, "{:02}", tm.tm_min),
+ 'm' => write!(fmt, "{:02}", tm.tm_mon + 1),
+ 'n' => fmt.write_str("\n"),
+ 'P' => fmt.write_str(if tm.tm_hour < 12 { "am" } else { "pm" }),
+ 'p' => fmt.write_str(if (tm.tm_hour) < 12 { "AM" } else { "PM" }),
+ 'R' => {
+ parse_type(fmt, 'H', tm)?;
+ fmt.write_str(":")?;
+ parse_type(fmt, 'M', tm)
+ }
+ 'r' => {
+ parse_type(fmt, 'I', tm)?;
+ fmt.write_str(":")?;
+ parse_type(fmt, 'M', tm)?;
+ fmt.write_str(":")?;
+ parse_type(fmt, 'S', tm)?;
+ fmt.write_str(" ")?;
+ parse_type(fmt, 'p', tm)
+ }
+ 'S' => write!(fmt, "{:02}", tm.tm_sec),
+ 's' => write!(fmt, "{}", tm.to_timespec().sec),
+ 'T' | 'X' => {
+ parse_type(fmt, 'H', tm)?;
+ fmt.write_str(":")?;
+ parse_type(fmt, 'M', tm)?;
+ fmt.write_str(":")?;
+ parse_type(fmt, 'S', tm)
+ }
+ 't' => fmt.write_str("\t"),
+ 'U' => write!(fmt, "{:02}", (tm.tm_yday - tm.tm_wday + 7) / 7),
+ 'u' => {
+ let i = tm.tm_wday;
+ write!(fmt, "{}", (if i == 0 { 7 } else { i }))
+ }
+ 'V' => iso_week(fmt, 'V', tm),
+ 'v' => {
+ parse_type(fmt, 'e', tm)?;
+ fmt.write_str("-")?;
+ parse_type(fmt, 'b', tm)?;
+ fmt.write_str("-")?;
+ parse_type(fmt, 'Y', tm)
+ }
+ 'W' => {
+ write!(fmt, "{:02}", (tm.tm_yday - (tm.tm_wday - 1 + 7) % 7 + 7) / 7)
+ }
+ 'w' => write!(fmt, "{}", tm.tm_wday),
+ 'Y' => write!(fmt, "{}", tm.tm_year + 1900),
+ 'y' => write!(fmt, "{:02}", (tm.tm_year + 1900) % 100),
+ // FIXME (#2350): support locale
+ 'Z' => fmt.write_str(if tm.tm_utcoff == 0 { "UTC"} else { "" }),
+ 'z' => {
+ let sign = if tm.tm_utcoff > 0 { '+' } else { '-' };
+ let mut m = abs(tm.tm_utcoff) / 60;
+ let h = m / 60;
+ m -= h * 60;
+ write!(fmt, "{}{:02}{:02}", sign, h, m)
+ }
+ '+' => write!(fmt, "{}", tm.rfc3339()),
+ '%' => fmt.write_str("%"),
+ _ => unreachable!(),
+ }
+}
+
+fn abs(i: i32) -> i32 {
+ if i < 0 {-i} else {i}
+}
diff --git a/third_party/rust/time-0.1.45/src/duration.rs b/third_party/rust/time-0.1.45/src/duration.rs
new file mode 100644
index 0000000000..b33206c1e6
--- /dev/null
+++ b/third_party/rust/time-0.1.45/src/duration.rs
@@ -0,0 +1,655 @@
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Temporal quantification
+
+use std::{fmt, i64};
+use std::error::Error;
+use std::ops::{Add, Sub, Mul, Div, Neg, FnOnce};
+use std::time::Duration as StdDuration;
+
+/// The number of nanoseconds in a microsecond.
+const NANOS_PER_MICRO: i32 = 1000;
+/// The number of nanoseconds in a millisecond.
+const NANOS_PER_MILLI: i32 = 1000_000;
+/// The number of nanoseconds in seconds.
+const NANOS_PER_SEC: i32 = 1_000_000_000;
+/// The number of microseconds per second.
+const MICROS_PER_SEC: i64 = 1000_000;
+/// The number of milliseconds per second.
+const MILLIS_PER_SEC: i64 = 1000;
+/// The number of seconds in a minute.
+const SECS_PER_MINUTE: i64 = 60;
+/// The number of seconds in an hour.
+const SECS_PER_HOUR: i64 = 3600;
+/// The number of (non-leap) seconds in days.
+const SECS_PER_DAY: i64 = 86400;
+/// The number of (non-leap) seconds in a week.
+const SECS_PER_WEEK: i64 = 604800;
+
+macro_rules! try_opt {
+ ($e:expr) => (match $e { Some(v) => v, None => return None })
+}
+
+
+/// ISO 8601 time duration with nanosecond precision.
+/// This also allows for the negative duration; see individual methods for details.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
+pub struct Duration {
+ secs: i64,
+ nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC
+}
+
+/// The minimum possible `Duration`: `i64::MIN` milliseconds.
+pub const MIN: Duration = Duration {
+ secs: i64::MIN / MILLIS_PER_SEC - 1,
+ nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI
+};
+
+/// The maximum possible `Duration`: `i64::MAX` milliseconds.
+pub const MAX: Duration = Duration {
+ secs: i64::MAX / MILLIS_PER_SEC,
+ nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI
+};
+
+impl Duration {
+ /// Makes a new `Duration` with given number of weeks.
+ /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks.
+ /// Panics when the duration is out of bounds.
+ #[inline]
+ pub fn weeks(weeks: i64) -> Duration {
+ let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds");
+ Duration::seconds(secs)
+ }
+
+ /// Makes a new `Duration` with given number of days.
+ /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks.
+ /// Panics when the duration is out of bounds.
+ #[inline]
+ pub fn days(days: i64) -> Duration {
+ let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds");
+ Duration::seconds(secs)
+ }
+
+ /// Makes a new `Duration` with given number of hours.
+ /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks.
+ /// Panics when the duration is out of bounds.
+ #[inline]
+ pub fn hours(hours: i64) -> Duration {
+ let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours out of bounds");
+ Duration::seconds(secs)
+ }
+
+ /// Makes a new `Duration` with given number of minutes.
+ /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks.
+ /// Panics when the duration is out of bounds.
+ #[inline]
+ pub fn minutes(minutes: i64) -> Duration {
+ let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds");
+ Duration::seconds(secs)
+ }
+
+ /// Makes a new `Duration` with given number of seconds.
+ /// Panics when the duration is more than `i64::MAX` milliseconds
+ /// or less than `i64::MIN` milliseconds.
+ #[inline]
+ pub fn seconds(seconds: i64) -> Duration {
+ let d = Duration { secs: seconds, nanos: 0 };
+ if d < MIN || d > MAX {
+ panic!("Duration::seconds out of bounds");
+ }
+ d
+ }
+
+ /// Makes a new `Duration` with given number of milliseconds.
+ #[inline]
+ pub fn milliseconds(milliseconds: i64) -> Duration {
+ let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC);
+ let nanos = millis as i32 * NANOS_PER_MILLI;
+ Duration { secs: secs, nanos: nanos }
+ }
+
+ /// Makes a new `Duration` with given number of microseconds.
+ #[inline]
+ pub fn microseconds(microseconds: i64) -> Duration {
+ let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
+ let nanos = micros as i32 * NANOS_PER_MICRO;
+ Duration { secs: secs, nanos: nanos }
+ }
+
+ /// Makes a new `Duration` with given number of nanoseconds.
+ #[inline]
+ pub fn nanoseconds(nanos: i64) -> Duration {
+ let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64);
+ Duration { secs: secs, nanos: nanos as i32 }
+ }
+
+ /// Runs a closure, returning the duration of time it took to run the
+ /// closure.
+ pub fn span<F>(f: F) -> Duration where F: FnOnce() {
+ let before = super::precise_time_ns();
+ f();
+ Duration::nanoseconds((super::precise_time_ns() - before) as i64)
+ }
+
+ /// Returns the total number of whole weeks in the duration.
+ #[inline]
+ pub fn num_weeks(&self) -> i64 {
+ self.num_days() / 7
+ }
+
+ /// Returns the total number of whole days in the duration.
+ pub fn num_days(&self) -> i64 {
+ self.num_seconds() / SECS_PER_DAY
+ }
+
+ /// Returns the total number of whole hours in the duration.
+ #[inline]
+ pub fn num_hours(&self) -> i64 {
+ self.num_seconds() / SECS_PER_HOUR
+ }
+
+ /// Returns the total number of whole minutes in the duration.
+ #[inline]
+ pub fn num_minutes(&self) -> i64 {
+ self.num_seconds() / SECS_PER_MINUTE
+ }
+
+ /// Returns the total number of whole seconds in the duration.
+ pub fn num_seconds(&self) -> i64 {
+ // If secs is negative, nanos should be subtracted from the duration.
+ if self.secs < 0 && self.nanos > 0 {
+ self.secs + 1
+ } else {
+ self.secs
+ }
+ }
+
+ /// Returns the number of nanoseconds such that
+ /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of
+ /// nanoseconds in the duration.
+ fn nanos_mod_sec(&self) -> i32 {
+ if self.secs < 0 && self.nanos > 0 {
+ self.nanos - NANOS_PER_SEC
+ } else {
+ self.nanos
+ }
+ }
+
+ /// Returns the total number of whole milliseconds in the duration,
+ pub fn num_milliseconds(&self) -> i64 {
+ // A proper Duration will not overflow, because MIN and MAX are defined
+ // such that the range is exactly i64 milliseconds.
+ let secs_part = self.num_seconds() * MILLIS_PER_SEC;
+ let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI;
+ secs_part + nanos_part as i64
+ }
+
+ /// Returns the total number of whole microseconds in the duration,
+ /// or `None` on overflow (exceeding 2<sup>63</sup> microseconds in either direction).
+ pub fn num_microseconds(&self) -> Option<i64> {
+ let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC));
+ let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO;
+ secs_part.checked_add(nanos_part as i64)
+ }
+
+ /// Returns the total number of whole nanoseconds in the duration,
+ /// or `None` on overflow (exceeding 2<sup>63</sup> nanoseconds in either direction).
+ pub fn num_nanoseconds(&self) -> Option<i64> {
+ let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64));
+ let nanos_part = self.nanos_mod_sec();
+ secs_part.checked_add(nanos_part as i64)
+ }
+
+ /// Add two durations, returning `None` if overflow occurred.
+ pub fn checked_add(&self, rhs: &Duration) -> Option<Duration> {
+ let mut secs = try_opt!(self.secs.checked_add(rhs.secs));
+ let mut nanos = self.nanos + rhs.nanos;
+ if nanos >= NANOS_PER_SEC {
+ nanos -= NANOS_PER_SEC;
+ secs = try_opt!(secs.checked_add(1));
+ }
+ let d = Duration { secs: secs, nanos: nanos };
+ // Even if d is within the bounds of i64 seconds,
+ // it might still overflow i64 milliseconds.
+ if d < MIN || d > MAX { None } else { Some(d) }
+ }
+
+ /// Subtract two durations, returning `None` if overflow occurred.
+ pub fn checked_sub(&self, rhs: &Duration) -> Option<Duration> {
+ let mut secs = try_opt!(self.secs.checked_sub(rhs.secs));
+ let mut nanos = self.nanos - rhs.nanos;
+ if nanos < 0 {
+ nanos += NANOS_PER_SEC;
+ secs = try_opt!(secs.checked_sub(1));
+ }
+ let d = Duration { secs: secs, nanos: nanos };
+ // Even if d is within the bounds of i64 seconds,
+ // it might still overflow i64 milliseconds.
+ if d < MIN || d > MAX { None } else { Some(d) }
+ }
+
+ /// The minimum possible `Duration`: `i64::MIN` milliseconds.
+ #[inline]
+ pub fn min_value() -> Duration { MIN }
+
+ /// The maximum possible `Duration`: `i64::MAX` milliseconds.
+ #[inline]
+ pub fn max_value() -> Duration { MAX }
+
+ /// A duration where the stored seconds and nanoseconds are equal to zero.
+ #[inline]
+ pub fn zero() -> Duration {
+ Duration { secs: 0, nanos: 0 }
+ }
+
+ /// Returns `true` if the duration equals `Duration::zero()`.
+ #[inline]
+ pub fn is_zero(&self) -> bool {
+ self.secs == 0 && self.nanos == 0
+ }
+
+ /// Creates a `time::Duration` object from `std::time::Duration`
+ ///
+ /// This function errors when original duration is larger than the maximum
+ /// value supported for this type.
+ pub fn from_std(duration: StdDuration) -> Result<Duration, OutOfRangeError> {
+ // We need to check secs as u64 before coercing to i64
+ if duration.as_secs() > MAX.secs as u64 {
+ return Err(OutOfRangeError(()));
+ }
+ let d = Duration {
+ secs: duration.as_secs() as i64,
+ nanos: duration.subsec_nanos() as i32,
+ };
+ if d > MAX {
+ return Err(OutOfRangeError(()));
+ }
+ Ok(d)
+ }
+
+ /// Creates a `std::time::Duration` object from `time::Duration`
+ ///
+ /// This function errors when duration is less than zero. As standard
+ /// library implementation is limited to non-negative values.
+ pub fn to_std(&self) -> Result<StdDuration, OutOfRangeError> {
+ if self.secs < 0 {
+ return Err(OutOfRangeError(()));
+ }
+ Ok(StdDuration::new(self.secs as u64, self.nanos as u32))
+ }
+
+ /// Returns the raw value of duration.
+ #[cfg(target_env = "sgx")]
+ pub(crate) fn raw(&self) -> (i64, i32) {
+ (self.secs, self.nanos)
+ }
+}
+
+impl Neg for Duration {
+ type Output = Duration;
+
+ #[inline]
+ fn neg(self) -> Duration {
+ if self.nanos == 0 {
+ Duration { secs: -self.secs, nanos: 0 }
+ } else {
+ Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos }
+ }
+ }
+}
+
+impl Add for Duration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Duration {
+ let mut secs = self.secs + rhs.secs;
+ let mut nanos = self.nanos + rhs.nanos;
+ if nanos >= NANOS_PER_SEC {
+ nanos -= NANOS_PER_SEC;
+ secs += 1;
+ }
+ Duration { secs: secs, nanos: nanos }
+ }
+}
+
+impl Sub for Duration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Duration {
+ let mut secs = self.secs - rhs.secs;
+ let mut nanos = self.nanos - rhs.nanos;
+ if nanos < 0 {
+ nanos += NANOS_PER_SEC;
+ secs -= 1;
+ }
+ Duration { secs: secs, nanos: nanos }
+ }
+}
+
+impl Mul<i32> for Duration {
+ type Output = Duration;
+
+ fn mul(self, rhs: i32) -> Duration {
+ // Multiply nanoseconds as i64, because it cannot overflow that way.
+ let total_nanos = self.nanos as i64 * rhs as i64;
+ let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
+ let secs = self.secs * rhs as i64 + extra_secs;
+ Duration { secs: secs, nanos: nanos as i32 }
+ }
+}
+
+impl Div<i32> for Duration {
+ type Output = Duration;
+
+ fn div(self, rhs: i32) -> Duration {
+ let mut secs = self.secs / rhs as i64;
+ let carry = self.secs - secs * rhs as i64;
+ let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
+ let mut nanos = self.nanos / rhs + extra_nanos as i32;
+ if nanos >= NANOS_PER_SEC {
+ nanos -= NANOS_PER_SEC;
+ secs += 1;
+ }
+ if nanos < 0 {
+ nanos += NANOS_PER_SEC;
+ secs -= 1;
+ }
+ Duration { secs: secs, nanos: nanos }
+ }
+}
+
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // technically speaking, negative duration is not valid ISO 8601,
+ // but we need to print it anyway.
+ let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") };
+
+ let days = abs.secs / SECS_PER_DAY;
+ let secs = abs.secs - days * SECS_PER_DAY;
+ let hasdate = days != 0;
+ let hastime = (secs != 0 || abs.nanos != 0) || !hasdate;
+
+ write!(f, "{}P", sign)?;
+
+ if hasdate {
+ write!(f, "{}D", days)?;
+ }
+ if hastime {
+ if abs.nanos == 0 {
+ write!(f, "T{}S", secs)?;
+ } else if abs.nanos % NANOS_PER_MILLI == 0 {
+ write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?;
+ } else if abs.nanos % NANOS_PER_MICRO == 0 {
+ write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?;
+ } else {
+ write!(f, "T{}.{:09}S", secs, abs.nanos)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+/// Represents error when converting `Duration` to/from a standard library
+/// implementation
+///
+/// The `std::time::Duration` supports a range from zero to `u64::MAX`
+/// *seconds*, while this module supports signed range of up to
+/// `i64::MAX` of *milliseconds*.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OutOfRangeError(());
+
+impl fmt::Display for OutOfRangeError {
+ #[allow(deprecated)]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.description())
+ }
+}
+
+impl Error for OutOfRangeError {
+ fn description(&self) -> &str {
+ "Source duration value is out of range for the target type"
+ }
+}
+
+// Copied from libnum
+#[inline]
+fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) {
+ (div_floor_64(this, other), mod_floor_64(this, other))
+}
+
+#[inline]
+fn div_floor_64(this: i64, other: i64) -> i64 {
+ match div_rem_64(this, other) {
+ (d, r) if (r > 0 && other < 0)
+ || (r < 0 && other > 0) => d - 1,
+ (d, _) => d,
+ }
+}
+
+#[inline]
+fn mod_floor_64(this: i64, other: i64) -> i64 {
+ match this % other {
+ r if (r > 0 && other < 0)
+ || (r < 0 && other > 0) => r + other,
+ r => r,
+ }
+}
+
+#[inline]
+fn div_rem_64(this: i64, other: i64) -> (i64, i64) {
+ (this / other, this % other)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Duration, MIN, MAX, OutOfRangeError};
+ use std::{i32, i64};
+ use std::time::Duration as StdDuration;
+
+ #[test]
+ fn test_duration() {
+ assert!(Duration::seconds(1) != Duration::zero());
+ assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3));
+ assert_eq!(Duration::seconds(86399) + Duration::seconds(4),
+ Duration::days(1) + Duration::seconds(3));
+ assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000));
+ assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000));
+ assert_eq!(Duration::days(2) + Duration::seconds(86399) +
+ Duration::nanoseconds(1234567890),
+ Duration::days(3) + Duration::nanoseconds(234567890));
+ assert_eq!(-Duration::days(3), Duration::days(-3));
+ assert_eq!(-(Duration::days(3) + Duration::seconds(70)),
+ Duration::days(-4) + Duration::seconds(86400-70));
+ }
+
+ #[test]
+ fn test_duration_num_days() {
+ assert_eq!(Duration::zero().num_days(), 0);
+ assert_eq!(Duration::days(1).num_days(), 1);
+ assert_eq!(Duration::days(-1).num_days(), -1);
+ assert_eq!(Duration::seconds(86399).num_days(), 0);
+ assert_eq!(Duration::seconds(86401).num_days(), 1);
+ assert_eq!(Duration::seconds(-86399).num_days(), 0);
+ assert_eq!(Duration::seconds(-86401).num_days(), -1);
+ assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64);
+ assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64);
+ }
+
+ #[test]
+ fn test_duration_num_seconds() {
+ assert_eq!(Duration::zero().num_seconds(), 0);
+ assert_eq!(Duration::seconds(1).num_seconds(), 1);
+ assert_eq!(Duration::seconds(-1).num_seconds(), -1);
+ assert_eq!(Duration::milliseconds(999).num_seconds(), 0);
+ assert_eq!(Duration::milliseconds(1001).num_seconds(), 1);
+ assert_eq!(Duration::milliseconds(-999).num_seconds(), 0);
+ assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1);
+ }
+
+ #[test]
+ fn test_duration_num_milliseconds() {
+ assert_eq!(Duration::zero().num_milliseconds(), 0);
+ assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1);
+ assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1);
+ assert_eq!(Duration::microseconds(999).num_milliseconds(), 0);
+ assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1);
+ assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0);
+ assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1);
+ assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX);
+ assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN);
+ assert_eq!(MAX.num_milliseconds(), i64::MAX);
+ assert_eq!(MIN.num_milliseconds(), i64::MIN);
+ }
+
+ #[test]
+ fn test_duration_num_microseconds() {
+ assert_eq!(Duration::zero().num_microseconds(), Some(0));
+ assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1));
+ assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1));
+ assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0));
+ assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1));
+ assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0));
+ assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1));
+ assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX));
+ assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN));
+ assert_eq!(MAX.num_microseconds(), None);
+ assert_eq!(MIN.num_microseconds(), None);
+
+ // overflow checks
+ const MICROS_PER_DAY: i64 = 86400_000_000;
+ assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(),
+ Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY));
+ assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(),
+ Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY));
+ assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None);
+ assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None);
+ }
+
+ #[test]
+ fn test_duration_num_nanoseconds() {
+ assert_eq!(Duration::zero().num_nanoseconds(), Some(0));
+ assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1));
+ assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1));
+ assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX));
+ assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN));
+ assert_eq!(MAX.num_nanoseconds(), None);
+ assert_eq!(MIN.num_nanoseconds(), None);
+
+ // overflow checks
+ const NANOS_PER_DAY: i64 = 86400_000_000_000;
+ assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(),
+ Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY));
+ assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(),
+ Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY));
+ assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None);
+ assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None);
+ }
+
+ #[test]
+ fn test_duration_checked_ops() {
+ assert_eq!(Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)),
+ Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)));
+ assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::microseconds(1000))
+ .is_none());
+
+ assert_eq!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)),
+ Some(Duration::milliseconds(i64::MIN)));
+ assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1))
+ .is_none());
+ }
+
+ #[test]
+ fn test_duration_mul() {
+ assert_eq!(Duration::zero() * i32::MAX, Duration::zero());
+ assert_eq!(Duration::zero() * i32::MIN, Duration::zero());
+ assert_eq!(Duration::nanoseconds(1) * 0, Duration::zero());
+ assert_eq!(Duration::nanoseconds(1) * 1, Duration::nanoseconds(1));
+ assert_eq!(Duration::nanoseconds(1) * 1_000_000_000, Duration::seconds(1));
+ assert_eq!(Duration::nanoseconds(1) * -1_000_000_000, -Duration::seconds(1));
+ assert_eq!(-Duration::nanoseconds(1) * 1_000_000_000, -Duration::seconds(1));
+ assert_eq!(Duration::nanoseconds(30) * 333_333_333,
+ Duration::seconds(10) - Duration::nanoseconds(10));
+ assert_eq!((Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3,
+ Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3));
+ assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3));
+ assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3));
+ }
+
+ #[test]
+ fn test_duration_div() {
+ assert_eq!(Duration::zero() / i32::MAX, Duration::zero());
+ assert_eq!(Duration::zero() / i32::MIN, Duration::zero());
+ assert_eq!(Duration::nanoseconds(123_456_789) / 1, Duration::nanoseconds(123_456_789));
+ assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789));
+ assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789));
+ assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789));
+ assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333));
+ assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333));
+ assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500));
+ assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500));
+ assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500));
+ assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333));
+ assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333));
+ }
+
+ #[test]
+ fn test_duration_fmt() {
+ assert_eq!(Duration::zero().to_string(), "PT0S");
+ assert_eq!(Duration::days(42).to_string(), "P42D");
+ assert_eq!(Duration::days(-42).to_string(), "-P42D");
+ assert_eq!(Duration::seconds(42).to_string(), "PT42S");
+ assert_eq!(Duration::milliseconds(42).to_string(), "PT0.042S");
+ assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S");
+ assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S");
+ assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(),
+ "P7DT6.543S");
+ assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S");
+ assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S");
+
+ // the format specifier should have no effect on `Duration`
+ assert_eq!(format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)),
+ "P1DT2.345S");
+ }
+
+ #[test]
+ fn test_to_std() {
+ assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0)));
+ assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0)));
+ assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000)));
+ assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000)));
+ assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777)));
+ assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000)));
+ assert_eq!(Duration::seconds(-1).to_std(),
+ Err(OutOfRangeError(())));
+ assert_eq!(Duration::milliseconds(-1).to_std(),
+ Err(OutOfRangeError(())));
+ }
+
+ #[test]
+ fn test_from_std() {
+ assert_eq!(Ok(Duration::seconds(1)),
+ Duration::from_std(StdDuration::new(1, 0)));
+ assert_eq!(Ok(Duration::seconds(86401)),
+ Duration::from_std(StdDuration::new(86401, 0)));
+ assert_eq!(Ok(Duration::milliseconds(123)),
+ Duration::from_std(StdDuration::new(0, 123000000)));
+ assert_eq!(Ok(Duration::milliseconds(123765)),
+ Duration::from_std(StdDuration::new(123, 765000000)));
+ assert_eq!(Ok(Duration::nanoseconds(777)),
+ Duration::from_std(StdDuration::new(0, 777)));
+ assert_eq!(Ok(MAX),
+ Duration::from_std(StdDuration::new(9223372036854775, 807000000)));
+ assert_eq!(Duration::from_std(StdDuration::new(9223372036854776, 0)),
+ Err(OutOfRangeError(())));
+ assert_eq!(Duration::from_std(StdDuration::new(9223372036854775, 807000001)),
+ Err(OutOfRangeError(())));
+ }
+}
diff --git a/third_party/rust/time-0.1.45/src/lib.rs b/third_party/rust/time-0.1.45/src/lib.rs
new file mode 100644
index 0000000000..c5a937427f
--- /dev/null
+++ b/third_party/rust/time-0.1.45/src/lib.rs
@@ -0,0 +1,1283 @@
+// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Simple time handling.
+//!
+//! # Usage
+//!
+//! This crate is [on crates.io](https://crates.io/crates/time) and can be
+//! used by adding `time` to the dependencies in your project's `Cargo.toml`.
+//!
+//! ```toml
+//! [dependencies]
+//! time = "0.1"
+//! ```
+//!
+//! And this in your crate root:
+//!
+//! ```rust
+//! extern crate time;
+//! ```
+//!
+//! This crate uses the same syntax for format strings as the
+//! [`strftime()`](http://man7.org/linux/man-pages/man3/strftime.3.html)
+//! function from the C standard library.
+
+#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
+ html_favicon_url = "https://www.rust-lang.org/favicon.ico",
+ html_root_url = "https://doc.rust-lang.org/time/")]
+#![allow(unknown_lints)]
+#![allow(ellipsis_inclusive_range_patterns)] // `..=` requires Rust 1.26
+#![allow(trivial_numeric_casts)]
+
+#[cfg(unix)] extern crate libc;
+#[cfg(windows)] extern crate winapi;
+#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize;
+
+#[cfg(target_os = "wasi")] extern crate wasi;
+
+#[cfg(test)] #[macro_use] extern crate log;
+
+use std::cmp::Ordering;
+use std::error::Error;
+use std::fmt;
+use std::ops::{Add, Sub};
+
+pub use duration::{Duration, OutOfRangeError};
+
+use self::ParseError::{InvalidDay, InvalidDayOfMonth, InvalidDayOfWeek,
+ InvalidDayOfYear, InvalidFormatSpecifier, InvalidHour,
+ InvalidMinute, InvalidMonth, InvalidSecond, InvalidTime,
+ InvalidYear, InvalidZoneOffset, InvalidSecondsSinceEpoch,
+ MissingFormatConverter, UnexpectedCharacter};
+
+pub use parse::strptime;
+
+mod display;
+mod duration;
+mod parse;
+mod sys;
+
+static NSEC_PER_SEC: i32 = 1_000_000_000;
+
+/// A record specifying a time value in seconds and nanoseconds, where
+/// nanoseconds represent the offset from the given second.
+///
+/// For example a timespec of 1.2 seconds after the beginning of the epoch would
+/// be represented as {sec: 1, nsec: 200000000}.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
+#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
+pub struct Timespec { pub sec: i64, pub nsec: i32 }
+/*
+ * Timespec assumes that pre-epoch Timespecs have negative sec and positive
+ * nsec fields. Darwin's and Linux's struct timespec functions handle pre-
+ * epoch timestamps using a "two steps back, one step forward" representation,
+ * though the man pages do not actually document this. For example, the time
+ * -1.2 seconds before the epoch is represented by `Timespec { sec: -2_i64,
+ * nsec: 800_000_000 }`.
+ */
+impl Timespec {
+ pub fn new(sec: i64, nsec: i32) -> Timespec {
+ assert!(nsec >= 0 && nsec < NSEC_PER_SEC);
+ Timespec { sec: sec, nsec: nsec }
+ }
+}
+
+impl Add<Duration> for Timespec {
+ type Output = Timespec;
+
+ fn add(self, other: Duration) -> Timespec {
+ let d_sec = other.num_seconds();
+ // It is safe to unwrap the nanoseconds, because there cannot be
+ // more than one second left, which fits in i64 and in i32.
+ let d_nsec = (other - Duration::seconds(d_sec))
+ .num_nanoseconds().unwrap() as i32;
+ let mut sec = self.sec + d_sec;
+ let mut nsec = self.nsec + d_nsec;
+ if nsec >= NSEC_PER_SEC {
+ nsec -= NSEC_PER_SEC;
+ sec += 1;
+ } else if nsec < 0 {
+ nsec += NSEC_PER_SEC;
+ sec -= 1;
+ }
+ Timespec::new(sec, nsec)
+ }
+}
+
+impl Sub<Duration> for Timespec {
+ type Output = Timespec;
+
+ fn sub(self, other: Duration) -> Timespec {
+ let d_sec = other.num_seconds();
+ // It is safe to unwrap the nanoseconds, because there cannot be
+ // more than one second left, which fits in i64 and in i32.
+ let d_nsec = (other - Duration::seconds(d_sec))
+ .num_nanoseconds().unwrap() as i32;
+ let mut sec = self.sec - d_sec;
+ let mut nsec = self.nsec - d_nsec;
+ if nsec >= NSEC_PER_SEC {
+ nsec -= NSEC_PER_SEC;
+ sec += 1;
+ } else if nsec < 0 {
+ nsec += NSEC_PER_SEC;
+ sec -= 1;
+ }
+ Timespec::new(sec, nsec)
+ }
+}
+
+impl Sub<Timespec> for Timespec {
+ type Output = Duration;
+
+ fn sub(self, other: Timespec) -> Duration {
+ let sec = self.sec - other.sec;
+ let nsec = self.nsec - other.nsec;
+ Duration::seconds(sec) + Duration::nanoseconds(nsec as i64)
+ }
+}
+
+/**
+ * Returns the current time as a `timespec` containing the seconds and
+ * nanoseconds since 1970-01-01T00:00:00Z.
+ */
+pub fn get_time() -> Timespec {
+ let (sec, nsec) = sys::get_time();
+ Timespec::new(sec, nsec)
+}
+
+
+/**
+ * Returns the current value of a high-resolution performance counter
+ * in nanoseconds since an unspecified epoch.
+ */
+#[inline]
+pub fn precise_time_ns() -> u64 {
+ sys::get_precise_ns()
+}
+
+
+/**
+ * Returns the current value of a high-resolution performance counter
+ * in seconds since an unspecified epoch.
+ */
+pub fn precise_time_s() -> f64 {
+ return (precise_time_ns() as f64) / 1000000000.;
+}
+
+/// An opaque structure representing a moment in time.
+///
+/// The only operation that can be performed on a `PreciseTime` is the
+/// calculation of the `Duration` of time that lies between them.
+///
+/// # Examples
+///
+/// Repeatedly call a function for 1 second:
+///
+/// ```rust
+/// use time::{Duration, PreciseTime};
+/// # fn do_some_work() {}
+///
+/// let start = PreciseTime::now();
+///
+/// while start.to(PreciseTime::now()) < Duration::seconds(1) {
+/// do_some_work();
+/// }
+/// ```
+#[derive(Copy, Clone)]
+pub struct PreciseTime(u64);
+
+impl PreciseTime {
+ /// Returns a `PreciseTime` representing the current moment in time.
+ pub fn now() -> PreciseTime {
+ PreciseTime(precise_time_ns())
+ }
+
+ /// Returns a `Duration` representing the span of time from the value of
+ /// `self` to the value of `later`.
+ ///
+ /// # Notes
+ ///
+ /// If `later` represents a time before `self`, the result of this method
+ /// is unspecified.
+ ///
+ /// If `later` represents a time more than 293 years after `self`, the
+ /// result of this method is unspecified.
+ #[inline]
+ pub fn to(&self, later: PreciseTime) -> Duration {
+ // NB: even if later is less than self due to overflow, this will work
+ // since the subtraction will underflow properly as well.
+ //
+ // We could deal with the overflow when casting to an i64, but all that
+ // gets us is the ability to handle intervals of up to 584 years, which
+ // seems not very useful :)
+ Duration::nanoseconds((later.0 - self.0) as i64)
+ }
+}
+
+/// A structure representing a moment in time.
+///
+/// `SteadyTime`s are generated by a "steady" clock, that is, a clock which
+/// never experiences discontinuous jumps and for which time always flows at
+/// the same rate.
+///
+/// # Examples
+///
+/// Repeatedly call a function for 1 second:
+///
+/// ```rust
+/// # use time::{Duration, SteadyTime};
+/// # fn do_some_work() {}
+/// let start = SteadyTime::now();
+///
+/// while SteadyTime::now() - start < Duration::seconds(1) {
+/// do_some_work();
+/// }
+/// ```
+#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
+pub struct SteadyTime(sys::SteadyTime);
+
+impl SteadyTime {
+ /// Returns a `SteadyTime` representing the current moment in time.
+ pub fn now() -> SteadyTime {
+ SteadyTime(sys::SteadyTime::now())
+ }
+}
+
+impl fmt::Display for SteadyTime {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ // TODO: needs a display customization
+ fmt::Debug::fmt(self, fmt)
+ }
+}
+
+impl Sub for SteadyTime {
+ type Output = Duration;
+
+ fn sub(self, other: SteadyTime) -> Duration {
+ self.0 - other.0
+ }
+}
+
+impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+
+ fn sub(self, other: Duration) -> SteadyTime {
+ SteadyTime(self.0 - other)
+ }
+}
+
+impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+
+ fn add(self, other: Duration) -> SteadyTime {
+ SteadyTime(self.0 + other)
+ }
+}
+
+#[cfg(not(any(windows, target_env = "sgx")))]
+pub fn tzset() {
+ extern { fn tzset(); }
+ unsafe { tzset() }
+}
+
+
+#[cfg(any(windows, target_env = "sgx"))]
+pub fn tzset() {}
+
+/// Holds a calendar date and time broken down into its components (year, month,
+/// day, and so on), also called a broken-down time value.
+// FIXME: use c_int instead of i32?
+#[repr(C)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
+#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
+pub struct Tm {
+ /// Seconds after the minute - [0, 60]
+ pub tm_sec: i32,
+
+ /// Minutes after the hour - [0, 59]
+ pub tm_min: i32,
+
+ /// Hours after midnight - [0, 23]
+ pub tm_hour: i32,
+
+ /// Day of the month - [1, 31]
+ pub tm_mday: i32,
+
+ /// Months since January - [0, 11]
+ pub tm_mon: i32,
+
+ /// Years since 1900
+ pub tm_year: i32,
+
+ /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
+ pub tm_wday: i32,
+
+ /// Days since January 1 - [0, 365]
+ pub tm_yday: i32,
+
+ /// Daylight Saving Time flag.
+ ///
+ /// This value is positive if Daylight Saving Time is in effect, zero if
+ /// Daylight Saving Time is not in effect, and negative if this information
+ /// is not available.
+ pub tm_isdst: i32,
+
+ /// Identifies the time zone that was used to compute this broken-down time
+ /// value, including any adjustment for Daylight Saving Time. This is the
+ /// number of seconds east of UTC. For example, for U.S. Pacific Daylight
+ /// Time, the value is `-7*60*60 = -25200`.
+ pub tm_utcoff: i32,
+
+ /// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
+ pub tm_nsec: i32,
+}
+
+impl Add<Duration> for Tm {
+ type Output = Tm;
+
+ /// The resulting Tm is in UTC.
+ // FIXME: The resulting Tm should have the same timezone as `self`;
+ // however, we need a function such as `at_tm(clock: Timespec, offset: i32)`
+ // for this.
+ fn add(self, other: Duration) -> Tm {
+ at_utc(self.to_timespec() + other)
+ }
+}
+
+impl Sub<Duration> for Tm {
+ type Output = Tm;
+
+ /// The resulting Tm is in UTC.
+ // FIXME: The resulting Tm should have the same timezone as `self`;
+ // however, we need a function such as `at_tm(clock: Timespec, offset: i32)`
+ // for this.
+ fn sub(self, other: Duration) -> Tm {
+ at_utc(self.to_timespec() - other)
+ }
+}
+
+impl Sub<Tm> for Tm {
+ type Output = Duration;
+
+ fn sub(self, other: Tm) -> Duration {
+ self.to_timespec() - other.to_timespec()
+ }
+}
+
+impl PartialOrd for Tm {
+ fn partial_cmp(&self, other: &Tm) -> Option<Ordering> {
+ self.to_timespec().partial_cmp(&other.to_timespec())
+ }
+}
+
+impl Ord for Tm {
+ fn cmp(&self, other: &Tm) -> Ordering {
+ self.to_timespec().cmp(&other.to_timespec())
+ }
+}
+
+pub fn empty_tm() -> Tm {
+ Tm {
+ tm_sec: 0,
+ tm_min: 0,
+ tm_hour: 0,
+ tm_mday: 0,
+ tm_mon: 0,
+ tm_year: 0,
+ tm_wday: 0,
+ tm_yday: 0,
+ tm_isdst: 0,
+ tm_utcoff: 0,
+ tm_nsec: 0,
+ }
+}
+
+/// Returns the specified time in UTC
+pub fn at_utc(clock: Timespec) -> Tm {
+ let Timespec { sec, nsec } = clock;
+ let mut tm = empty_tm();
+ sys::time_to_utc_tm(sec, &mut tm);
+ tm.tm_nsec = nsec;
+ tm
+}
+
+/// Returns the current time in UTC
+pub fn now_utc() -> Tm {
+ at_utc(get_time())
+}
+
+/// Returns the specified time in the local timezone
+pub fn at(clock: Timespec) -> Tm {
+ let Timespec { sec, nsec } = clock;
+ let mut tm = empty_tm();
+ sys::time_to_local_tm(sec, &mut tm);
+ tm.tm_nsec = nsec;
+ tm
+}
+
+/// Returns the current time in the local timezone
+pub fn now() -> Tm {
+ at(get_time())
+}
+
+impl Tm {
+ /// Convert time to the seconds from January 1, 1970
+ pub fn to_timespec(&self) -> Timespec {
+ let sec = match self.tm_utcoff {
+ 0 => sys::utc_tm_to_time(self),
+ _ => sys::local_tm_to_time(self)
+ };
+
+ Timespec::new(sec, self.tm_nsec)
+ }
+
+ /// Convert time to the local timezone
+ pub fn to_local(&self) -> Tm {
+ at(self.to_timespec())
+ }
+
+ /// Convert time to the UTC
+ pub fn to_utc(&self) -> Tm {
+ match self.tm_utcoff {
+ 0 => *self,
+ _ => at_utc(self.to_timespec())
+ }
+ }
+
+ /**
+ * Returns a TmFmt that outputs according to the `asctime` format in ISO
+ * C, in the local timezone.
+ *
+ * Example: "Thu Jan 1 00:00:00 1970"
+ */
+ pub fn ctime(&self) -> TmFmt {
+ TmFmt {
+ tm: self,
+ format: Fmt::Ctime,
+ }
+ }
+
+ /**
+ * Returns a TmFmt that outputs according to the `asctime` format in ISO
+ * C.
+ *
+ * Example: "Thu Jan 1 00:00:00 1970"
+ */
+ pub fn asctime(&self) -> TmFmt {
+ TmFmt {
+ tm: self,
+ format: Fmt::Str("%c"),
+ }
+ }
+
+ /// Formats the time according to the format string.
+ pub fn strftime<'a>(&'a self, format: &'a str) -> Result<TmFmt<'a>, ParseError> {
+ validate_format(TmFmt {
+ tm: self,
+ format: Fmt::Str(format),
+ })
+ }
+
+ /**
+ * Returns a TmFmt that outputs according to RFC 822.
+ *
+ * local: "Thu, 22 Mar 2012 07:53:18 PST"
+ * utc: "Thu, 22 Mar 2012 14:53:18 GMT"
+ */
+ pub fn rfc822(&self) -> TmFmt {
+ let fmt = if self.tm_utcoff == 0 {
+ "%a, %d %b %Y %T GMT"
+ } else {
+ "%a, %d %b %Y %T %Z"
+ };
+ TmFmt {
+ tm: self,
+ format: Fmt::Str(fmt),
+ }
+ }
+
+ /**
+ * Returns a TmFmt that outputs according to RFC 822 with Zulu time.
+ *
+ * local: "Thu, 22 Mar 2012 07:53:18 -0700"
+ * utc: "Thu, 22 Mar 2012 14:53:18 -0000"
+ */
+ pub fn rfc822z(&self) -> TmFmt {
+ TmFmt {
+ tm: self,
+ format: Fmt::Str("%a, %d %b %Y %T %z"),
+ }
+ }
+
+ /**
+ * Returns a TmFmt that outputs according to RFC 3339. RFC 3339 is
+ * compatible with ISO 8601.
+ *
+ * local: "2012-02-22T07:53:18-07:00"
+ * utc: "2012-02-22T14:53:18Z"
+ */
+ pub fn rfc3339<'a>(&'a self) -> TmFmt {
+ TmFmt {
+ tm: self,
+ format: Fmt::Rfc3339,
+ }
+ }
+}
+
+#[derive(Copy, PartialEq, Debug, Clone)]
+pub enum ParseError {
+ InvalidSecond,
+ InvalidMinute,
+ InvalidHour,
+ InvalidDay,
+ InvalidMonth,
+ InvalidYear,
+ InvalidDayOfWeek,
+ InvalidDayOfMonth,
+ InvalidDayOfYear,
+ InvalidZoneOffset,
+ InvalidTime,
+ InvalidSecondsSinceEpoch,
+ MissingFormatConverter,
+ InvalidFormatSpecifier(char),
+ UnexpectedCharacter(char, char),
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[allow(deprecated)]
+ match *self {
+ InvalidFormatSpecifier(ch) => {
+ write!(f, "{}: %{}", self.description(), ch)
+ }
+ UnexpectedCharacter(a, b) => {
+ write!(f, "expected: `{}`, found: `{}`", a, b)
+ }
+ _ => write!(f, "{}", self.description())
+ }
+ }
+}
+
+impl Error for ParseError {
+ fn description(&self) -> &str {
+ match *self {
+ InvalidSecond => "Invalid second.",
+ InvalidMinute => "Invalid minute.",
+ InvalidHour => "Invalid hour.",
+ InvalidDay => "Invalid day.",
+ InvalidMonth => "Invalid month.",
+ InvalidYear => "Invalid year.",
+ InvalidDayOfWeek => "Invalid day of the week.",
+ InvalidDayOfMonth => "Invalid day of the month.",
+ InvalidDayOfYear => "Invalid day of the year.",
+ InvalidZoneOffset => "Invalid zone offset.",
+ InvalidTime => "Invalid time.",
+ InvalidSecondsSinceEpoch => "Invalid seconds since epoch.",
+ MissingFormatConverter => "missing format converter after `%`",
+ InvalidFormatSpecifier(..) => "invalid format specifier",
+ UnexpectedCharacter(..) => "Unexpected character.",
+ }
+ }
+}
+
+/// A wrapper around a `Tm` and format string that implements Display.
+#[derive(Debug)]
+pub struct TmFmt<'a> {
+ tm: &'a Tm,
+ format: Fmt<'a>
+}
+
+#[derive(Debug)]
+enum Fmt<'a> {
+ Str(&'a str),
+ Rfc3339,
+ Ctime,
+}
+
+fn validate_format<'a>(fmt: TmFmt<'a>) -> Result<TmFmt<'a>, ParseError> {
+
+ match (fmt.tm.tm_wday, fmt.tm.tm_mon) {
+ (0...6, 0...11) => (),
+ (_wday, 0...11) => return Err(InvalidDayOfWeek),
+ (0...6, _mon) => return Err(InvalidMonth),
+ _ => return Err(InvalidDay)
+ }
+ match fmt.format {
+ Fmt::Str(ref s) => {
+ let mut chars = s.chars();
+ loop {
+ match chars.next() {
+ Some('%') => {
+ match chars.next() {
+ Some('A') | Some('a') | Some('B') | Some('b') |
+ Some('C') | Some('c') | Some('D') | Some('d') |
+ Some('e') | Some('F') | Some('f') | Some('G') |
+ Some('g') | Some('H') | Some('h') | Some('I') |
+ Some('j') | Some('k') | Some('l') | Some('M') |
+ Some('m') | Some('n') | Some('P') | Some('p') |
+ Some('R') | Some('r') | Some('S') | Some('s') |
+ Some('T') | Some('t') | Some('U') | Some('u') |
+ Some('V') | Some('v') | Some('W') | Some('w') |
+ Some('X') | Some('x') | Some('Y') | Some('y') |
+ Some('Z') | Some('z') | Some('+') | Some('%') => (),
+
+ Some(c) => return Err(InvalidFormatSpecifier(c)),
+ None => return Err(MissingFormatConverter),
+ }
+ },
+ None => break,
+ _ => ()
+ }
+ }
+ },
+ _ => ()
+ }
+ Ok(fmt)
+}
+
+/// Formats the time according to the format string.
+pub fn strftime(format: &str, tm: &Tm) -> Result<String, ParseError> {
+ tm.strftime(format).map(|fmt| fmt.to_string())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Timespec, get_time, precise_time_ns, precise_time_s,
+ at_utc, at, strptime, PreciseTime, SteadyTime, ParseError, Duration};
+ use super::ParseError::{InvalidTime, InvalidYear, MissingFormatConverter,
+ InvalidFormatSpecifier};
+
+ #[allow(deprecated)] // `Once::new` is const starting in Rust 1.32
+ use std::sync::ONCE_INIT;
+ use std::sync::{Once, Mutex, MutexGuard, LockResult};
+ use std::i32;
+ use std::mem;
+
+ struct TzReset {
+ _tzreset: ::sys::TzReset,
+ _lock: LockResult<MutexGuard<'static, ()>>,
+ }
+
+ fn set_time_zone_la_or_london(london: bool) -> TzReset {
+ // Lock manages current timezone because some tests require LA some
+ // London
+ static mut LOCK: *mut Mutex<()> = 0 as *mut _;
+ #[allow(deprecated)] // `Once::new` is const starting in Rust 1.32
+ static INIT: Once = ONCE_INIT;
+
+ unsafe {
+ INIT.call_once(|| {
+ LOCK = mem::transmute(Box::new(Mutex::new(())));
+ });
+
+ let timezone_lock = (*LOCK).lock();
+ let reset_func = if london {
+ ::sys::set_london_with_dst_time_zone()
+ } else {
+ ::sys::set_los_angeles_time_zone()
+ };
+ TzReset {
+ _lock: timezone_lock,
+ _tzreset: reset_func,
+ }
+ }
+ }
+
+ fn set_time_zone() -> TzReset {
+ set_time_zone_la_or_london(false)
+ }
+
+ fn set_time_zone_london_dst() -> TzReset {
+ set_time_zone_la_or_london(true)
+ }
+
+ #[test]
+ fn test_get_time() {
+ static SOME_RECENT_DATE: i64 = 1577836800i64; // 2020-01-01T00:00:00Z
+ static SOME_FUTURE_DATE: i64 = i32::MAX as i64; // Y2038
+
+ let tv1 = get_time();
+ debug!("tv1={} sec + {} nsec", tv1.sec, tv1.nsec);
+
+ assert!(tv1.sec > SOME_RECENT_DATE);
+ assert!(tv1.nsec < 1000000000i32);
+
+ let tv2 = get_time();
+ debug!("tv2={} sec + {} nsec", tv2.sec, tv2.nsec);
+
+ assert!(tv2.sec >= tv1.sec);
+ assert!(tv2.sec < SOME_FUTURE_DATE);
+ assert!(tv2.nsec < 1000000000i32);
+ if tv2.sec == tv1.sec {
+ assert!(tv2.nsec >= tv1.nsec);
+ }
+ }
+
+ #[test]
+ fn test_precise_time() {
+ let s0 = precise_time_s();
+ debug!("s0={} sec", s0);
+ assert!(s0 > 0.);
+
+ let ns0 = precise_time_ns();
+ let ns1 = precise_time_ns();
+ debug!("ns0={} ns", ns0);
+ debug!("ns1={} ns", ns1);
+ assert!(ns1 >= ns0);
+
+ let ns2 = precise_time_ns();
+ debug!("ns2={} ns", ns2);
+ assert!(ns2 >= ns1);
+ }
+
+ #[test]
+ fn test_precise_time_to() {
+ let t0 = PreciseTime(1000);
+ let t1 = PreciseTime(1023);
+ assert_eq!(Duration::nanoseconds(23), t0.to(t1));
+ }
+
+ #[test]
+ fn test_at_utc() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+
+ assert_eq!(utc.tm_sec, 30);
+ assert_eq!(utc.tm_min, 31);
+ assert_eq!(utc.tm_hour, 23);
+ assert_eq!(utc.tm_mday, 13);
+ assert_eq!(utc.tm_mon, 1);
+ assert_eq!(utc.tm_year, 109);
+ assert_eq!(utc.tm_wday, 5);
+ assert_eq!(utc.tm_yday, 43);
+ assert_eq!(utc.tm_isdst, 0);
+ assert_eq!(utc.tm_utcoff, 0);
+ assert_eq!(utc.tm_nsec, 54321);
+ }
+
+ #[test]
+ fn test_at() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let local = at(time);
+
+ debug!("time_at: {:?}", local);
+
+ assert_eq!(local.tm_sec, 30);
+ assert_eq!(local.tm_min, 31);
+ assert_eq!(local.tm_hour, 15);
+ assert_eq!(local.tm_mday, 13);
+ assert_eq!(local.tm_mon, 1);
+ assert_eq!(local.tm_year, 109);
+ assert_eq!(local.tm_wday, 5);
+ assert_eq!(local.tm_yday, 43);
+ assert_eq!(local.tm_isdst, 0);
+ assert_eq!(local.tm_utcoff, -28800);
+ assert_eq!(local.tm_nsec, 54321);
+ }
+
+ #[test]
+ fn test_to_timespec() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+
+ assert_eq!(utc.to_timespec(), time);
+ assert_eq!(utc.to_local().to_timespec(), time);
+ }
+
+ #[test]
+ fn test_conversions() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+ let local = at(time);
+
+ assert!(local.to_local() == local);
+ assert!(local.to_utc() == utc);
+ assert!(local.to_utc().to_local() == local);
+ assert!(utc.to_utc() == utc);
+ assert!(utc.to_local() == local);
+ assert!(utc.to_local().to_utc() == utc);
+ }
+
+ #[test]
+ fn test_strptime() {
+ let _reset = set_time_zone();
+
+ match strptime("", "") {
+ Ok(ref tm) => {
+ assert!(tm.tm_sec == 0);
+ assert!(tm.tm_min == 0);
+ assert!(tm.tm_hour == 0);
+ assert!(tm.tm_mday == 0);
+ assert!(tm.tm_mon == 0);
+ assert!(tm.tm_year == 0);
+ assert!(tm.tm_wday == 0);
+ assert!(tm.tm_isdst == 0);
+ assert!(tm.tm_utcoff == 0);
+ assert!(tm.tm_nsec == 0);
+ }
+ Err(_) => ()
+ }
+
+ let format = "%a %b %e %T.%f %Y";
+ assert_eq!(strptime("", format), Err(ParseError::InvalidDay));
+ assert_eq!(strptime("Fri Feb 13 15:31:30", format),
+ Err(InvalidTime));
+
+ match strptime("Fri Feb 13 15:31:30.01234 2009", format) {
+ Err(e) => panic!("{}", e),
+ Ok(ref tm) => {
+ assert_eq!(tm.tm_sec, 30);
+ assert_eq!(tm.tm_min, 31);
+ assert_eq!(tm.tm_hour, 15);
+ assert_eq!(tm.tm_mday, 13);
+ assert_eq!(tm.tm_mon, 1);
+ assert_eq!(tm.tm_year, 109);
+ assert_eq!(tm.tm_wday, 5);
+ assert_eq!(tm.tm_yday, 0);
+ assert_eq!(tm.tm_isdst, 0);
+ assert_eq!(tm.tm_utcoff, 0);
+ assert_eq!(tm.tm_nsec, 12340000);
+ }
+ }
+
+ fn test(s: &str, format: &str) -> bool {
+ match strptime(s, format) {
+ Ok(tm) => {
+ tm.strftime(format).unwrap().to_string() == s.to_string()
+ },
+ Err(e) => panic!("{:?}, s={:?}, format={:?}", e, s, format)
+ }
+ }
+
+ fn test_oneway(s : &str, format : &str) -> bool {
+ match strptime(s, format) {
+ Ok(_) => {
+ // oneway tests are used when reformatting the parsed Tm
+ // back into a string can generate a different string
+ // from the original (i.e. leading zeroes)
+ true
+ },
+ Err(e) => panic!("{:?}, s={:?}, format={:?}", e, s, format)
+ }
+ }
+
+ let days = [
+ "Sunday".to_string(),
+ "Monday".to_string(),
+ "Tuesday".to_string(),
+ "Wednesday".to_string(),
+ "Thursday".to_string(),
+ "Friday".to_string(),
+ "Saturday".to_string()
+ ];
+ for day in days.iter() {
+ assert!(test(&day, "%A"));
+ }
+
+ let days = [
+ "Sun".to_string(),
+ "Mon".to_string(),
+ "Tue".to_string(),
+ "Wed".to_string(),
+ "Thu".to_string(),
+ "Fri".to_string(),
+ "Sat".to_string()
+ ];
+ for day in days.iter() {
+ assert!(test(&day, "%a"));
+ }
+
+ let months = [
+ "January".to_string(),
+ "February".to_string(),
+ "March".to_string(),
+ "April".to_string(),
+ "May".to_string(),
+ "June".to_string(),
+ "July".to_string(),
+ "August".to_string(),
+ "September".to_string(),
+ "October".to_string(),
+ "November".to_string(),
+ "December".to_string()
+ ];
+ for day in months.iter() {
+ assert!(test(&day, "%B"));
+ }
+
+ let months = [
+ "Jan".to_string(),
+ "Feb".to_string(),
+ "Mar".to_string(),
+ "Apr".to_string(),
+ "May".to_string(),
+ "Jun".to_string(),
+ "Jul".to_string(),
+ "Aug".to_string(),
+ "Sep".to_string(),
+ "Oct".to_string(),
+ "Nov".to_string(),
+ "Dec".to_string()
+ ];
+ for day in months.iter() {
+ assert!(test(&day, "%b"));
+ }
+
+ assert!(test("19", "%C"));
+ assert!(test("Fri Feb 3 23:31:30 2009", "%c"));
+ assert!(test("Fri Feb 13 23:31:30 2009", "%c"));
+ assert!(test("02/13/09", "%D"));
+ assert!(test("03", "%d"));
+ assert!(test("13", "%d"));
+ assert!(test(" 3", "%e"));
+ assert!(test("13", "%e"));
+ assert!(test("2009-02-13", "%F"));
+ assert!(test("03", "%H"));
+ assert!(test("13", "%H"));
+ assert!(test("03", "%I")); // FIXME (#2350): flesh out
+ assert!(test("11", "%I")); // FIXME (#2350): flesh out
+ assert!(test("044", "%j"));
+ assert!(test(" 3", "%k"));
+ assert!(test("13", "%k"));
+ assert!(test(" 1", "%l"));
+ assert!(test("11", "%l"));
+ assert!(test("03", "%M"));
+ assert!(test("13", "%M"));
+ assert!(test("\n", "%n"));
+ assert!(test("am", "%P"));
+ assert!(test("pm", "%P"));
+ assert!(test("AM", "%p"));
+ assert!(test("PM", "%p"));
+ assert!(test("23:31", "%R"));
+ assert!(test("11:31:30 AM", "%r"));
+ assert!(test("11:31:30 PM", "%r"));
+ assert!(test("03", "%S"));
+ assert!(test("13", "%S"));
+ assert!(test("15:31:30", "%T"));
+ assert!(test("\t", "%t"));
+ assert!(test("1", "%u"));
+ assert!(test("7", "%u"));
+ assert!(test("13-Feb-2009", "%v"));
+ assert!(test("0", "%w"));
+ assert!(test("6", "%w"));
+ assert!(test("2009", "%Y"));
+ assert!(test("09", "%y"));
+
+ assert!(test_oneway("3", "%d"));
+ assert!(test_oneway("3", "%H"));
+ assert!(test_oneway("3", "%e"));
+ assert!(test_oneway("3", "%M"));
+ assert!(test_oneway("3", "%S"));
+
+ assert!(strptime("-0000", "%z").unwrap().tm_utcoff == 0);
+ assert!(strptime("-00:00", "%z").unwrap().tm_utcoff == 0);
+ assert!(strptime("Z", "%z").unwrap().tm_utcoff == 0);
+ assert_eq!(-28800, strptime("-0800", "%z").unwrap().tm_utcoff);
+ assert_eq!(-28800, strptime("-08:00", "%z").unwrap().tm_utcoff);
+ assert_eq!(28800, strptime("+0800", "%z").unwrap().tm_utcoff);
+ assert_eq!(28800, strptime("+08:00", "%z").unwrap().tm_utcoff);
+ assert_eq!(5400, strptime("+0130", "%z").unwrap().tm_utcoff);
+ assert_eq!(5400, strptime("+01:30", "%z").unwrap().tm_utcoff);
+ assert!(test("%", "%%"));
+
+ // Test for #7256
+ assert_eq!(strptime("360", "%Y-%m-%d"), Err(InvalidYear));
+
+ // Test for epoch seconds parsing
+ {
+ assert!(test("1428035610", "%s"));
+ let tm = strptime("1428035610", "%s").unwrap();
+ assert_eq!(tm.tm_utcoff, 0);
+ assert_eq!(tm.tm_isdst, 0);
+ assert_eq!(tm.tm_yday, 92);
+ assert_eq!(tm.tm_wday, 5);
+ assert_eq!(tm.tm_year, 115);
+ assert_eq!(tm.tm_mon, 3);
+ assert_eq!(tm.tm_mday, 3);
+ assert_eq!(tm.tm_hour, 4);
+ }
+ }
+
+ #[test]
+ fn test_asctime() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+ let local = at(time);
+
+ debug!("test_ctime: {} {}", utc.asctime(), local.asctime());
+
+ assert_eq!(utc.asctime().to_string(), "Fri Feb 13 23:31:30 2009".to_string());
+ assert_eq!(local.asctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ }
+
+ #[test]
+ fn test_ctime() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+ let local = at(time);
+
+ debug!("test_ctime: {} {}", utc.ctime(), local.ctime());
+
+ assert_eq!(utc.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ assert_eq!(local.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ }
+
+ #[test]
+ fn test_strftime() {
+ let _reset = set_time_zone();
+
+ let time = Timespec::new(1234567890, 54321);
+ let utc = at_utc(time);
+ let local = at(time);
+
+ assert_eq!(local.strftime("").unwrap().to_string(), "".to_string());
+ assert_eq!(local.strftime("%A").unwrap().to_string(), "Friday".to_string());
+ assert_eq!(local.strftime("%a").unwrap().to_string(), "Fri".to_string());
+ assert_eq!(local.strftime("%B").unwrap().to_string(), "February".to_string());
+ assert_eq!(local.strftime("%b").unwrap().to_string(), "Feb".to_string());
+ assert_eq!(local.strftime("%C").unwrap().to_string(), "20".to_string());
+ assert_eq!(local.strftime("%c").unwrap().to_string(),
+ "Fri Feb 13 15:31:30 2009".to_string());
+ assert_eq!(local.strftime("%D").unwrap().to_string(), "02/13/09".to_string());
+ assert_eq!(local.strftime("%d").unwrap().to_string(), "13".to_string());
+ assert_eq!(local.strftime("%e").unwrap().to_string(), "13".to_string());
+ assert_eq!(local.strftime("%F").unwrap().to_string(), "2009-02-13".to_string());
+ assert_eq!(local.strftime("%f").unwrap().to_string(), "000054321".to_string());
+ assert_eq!(local.strftime("%G").unwrap().to_string(), "2009".to_string());
+ assert_eq!(local.strftime("%g").unwrap().to_string(), "09".to_string());
+ assert_eq!(local.strftime("%H").unwrap().to_string(), "15".to_string());
+ assert_eq!(local.strftime("%h").unwrap().to_string(), "Feb".to_string());
+ assert_eq!(local.strftime("%I").unwrap().to_string(), "03".to_string());
+ assert_eq!(local.strftime("%j").unwrap().to_string(), "044".to_string());
+ assert_eq!(local.strftime("%k").unwrap().to_string(), "15".to_string());
+ assert_eq!(local.strftime("%l").unwrap().to_string(), " 3".to_string());
+ assert_eq!(local.strftime("%M").unwrap().to_string(), "31".to_string());
+ assert_eq!(local.strftime("%m").unwrap().to_string(), "02".to_string());
+ assert_eq!(local.strftime("%n").unwrap().to_string(), "\n".to_string());
+ assert_eq!(local.strftime("%P").unwrap().to_string(), "pm".to_string());
+ assert_eq!(local.strftime("%p").unwrap().to_string(), "PM".to_string());
+ assert_eq!(local.strftime("%R").unwrap().to_string(), "15:31".to_string());
+ assert_eq!(local.strftime("%r").unwrap().to_string(), "03:31:30 PM".to_string());
+ assert_eq!(local.strftime("%S").unwrap().to_string(), "30".to_string());
+ assert_eq!(local.strftime("%s").unwrap().to_string(), "1234567890".to_string());
+ assert_eq!(local.strftime("%T").unwrap().to_string(), "15:31:30".to_string());
+ assert_eq!(local.strftime("%t").unwrap().to_string(), "\t".to_string());
+ assert_eq!(local.strftime("%U").unwrap().to_string(), "06".to_string());
+ assert_eq!(local.strftime("%u").unwrap().to_string(), "5".to_string());
+ assert_eq!(local.strftime("%V").unwrap().to_string(), "07".to_string());
+ assert_eq!(local.strftime("%v").unwrap().to_string(), "13-Feb-2009".to_string());
+ assert_eq!(local.strftime("%W").unwrap().to_string(), "06".to_string());
+ assert_eq!(local.strftime("%w").unwrap().to_string(), "5".to_string());
+ // FIXME (#2350): support locale
+ assert_eq!(local.strftime("%X").unwrap().to_string(), "15:31:30".to_string());
+ // FIXME (#2350): support locale
+ assert_eq!(local.strftime("%x").unwrap().to_string(), "02/13/09".to_string());
+ assert_eq!(local.strftime("%Y").unwrap().to_string(), "2009".to_string());
+ assert_eq!(local.strftime("%y").unwrap().to_string(), "09".to_string());
+ // FIXME (#2350): support locale
+ assert_eq!(local.strftime("%Z").unwrap().to_string(), "".to_string());
+ assert_eq!(local.strftime("%z").unwrap().to_string(), "-0800".to_string());
+ assert_eq!(local.strftime("%+").unwrap().to_string(),
+ "2009-02-13T15:31:30-08:00".to_string());
+ assert_eq!(local.strftime("%%").unwrap().to_string(), "%".to_string());
+
+ let invalid_specifiers = ["%E", "%J", "%K", "%L", "%N", "%O", "%o", "%Q", "%q"];
+ for &sp in invalid_specifiers.iter() {
+ assert_eq!(local.strftime(sp).unwrap_err(),
+ InvalidFormatSpecifier(sp[1..].chars().next().unwrap()));
+ }
+ assert_eq!(local.strftime("%").unwrap_err(), MissingFormatConverter);
+ assert_eq!(local.strftime("%A %").unwrap_err(), MissingFormatConverter);
+
+ assert_eq!(local.asctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ assert_eq!(local.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ assert_eq!(local.rfc822z().to_string(), "Fri, 13 Feb 2009 15:31:30 -0800".to_string());
+ assert_eq!(local.rfc3339().to_string(), "2009-02-13T15:31:30-08:00".to_string());
+
+ assert_eq!(utc.asctime().to_string(), "Fri Feb 13 23:31:30 2009".to_string());
+ assert_eq!(utc.ctime().to_string(), "Fri Feb 13 15:31:30 2009".to_string());
+ assert_eq!(utc.rfc822().to_string(), "Fri, 13 Feb 2009 23:31:30 GMT".to_string());
+ assert_eq!(utc.rfc822z().to_string(), "Fri, 13 Feb 2009 23:31:30 -0000".to_string());
+ assert_eq!(utc.rfc3339().to_string(), "2009-02-13T23:31:30Z".to_string());
+ }
+
+ #[test]
+ fn test_timespec_eq_ord() {
+ let a = &Timespec::new(-2, 1);
+ let b = &Timespec::new(-1, 2);
+ let c = &Timespec::new(1, 2);
+ let d = &Timespec::new(2, 1);
+ let e = &Timespec::new(2, 1);
+
+ assert!(d.eq(e));
+ assert!(c.ne(e));
+
+ assert!(a.lt(b));
+ assert!(b.lt(c));
+ assert!(c.lt(d));
+
+ assert!(a.le(b));
+ assert!(b.le(c));
+ assert!(c.le(d));
+ assert!(d.le(e));
+ assert!(e.le(d));
+
+ assert!(b.ge(a));
+ assert!(c.ge(b));
+ assert!(d.ge(c));
+ assert!(e.ge(d));
+ assert!(d.ge(e));
+
+ assert!(b.gt(a));
+ assert!(c.gt(b));
+ assert!(d.gt(c));
+ }
+
+ #[test]
+ #[allow(deprecated)]
+ fn test_timespec_hash() {
+ use std::hash::{Hash, Hasher};
+
+ let c = &Timespec::new(3, 2);
+ let d = &Timespec::new(2, 1);
+ let e = &Timespec::new(2, 1);
+
+ let mut hasher = ::std::hash::SipHasher::new();
+
+ let d_hash:u64 = {
+ d.hash(&mut hasher);
+ hasher.finish()
+ };
+
+ hasher = ::std::hash::SipHasher::new();
+
+ let e_hash:u64 = {
+ e.hash(&mut hasher);
+ hasher.finish()
+ };
+
+ hasher = ::std::hash::SipHasher::new();
+
+ let c_hash:u64 = {
+ c.hash(&mut hasher);
+ hasher.finish()
+ };
+
+ assert_eq!(d_hash, e_hash);
+ assert!(c_hash != e_hash);
+ }
+
+ #[test]
+ fn test_timespec_add() {
+ let a = Timespec::new(1, 2);
+ let b = Duration::seconds(2) + Duration::nanoseconds(3);
+ let c = a + b;
+ assert_eq!(c.sec, 3);
+ assert_eq!(c.nsec, 5);
+
+ let p = Timespec::new(1, super::NSEC_PER_SEC - 2);
+ let q = Duration::seconds(2) + Duration::nanoseconds(2);
+ let r = p + q;
+ assert_eq!(r.sec, 4);
+ assert_eq!(r.nsec, 0);
+
+ let u = Timespec::new(1, super::NSEC_PER_SEC - 2);
+ let v = Duration::seconds(2) + Duration::nanoseconds(3);
+ let w = u + v;
+ assert_eq!(w.sec, 4);
+ assert_eq!(w.nsec, 1);
+
+ let k = Timespec::new(1, 0);
+ let l = Duration::nanoseconds(-1);
+ let m = k + l;
+ assert_eq!(m.sec, 0);
+ assert_eq!(m.nsec, 999_999_999);
+ }
+
+ #[test]
+ fn test_timespec_sub() {
+ let a = Timespec::new(2, 3);
+ let b = Timespec::new(1, 2);
+ let c = a - b;
+ assert_eq!(c.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 + 1));
+
+ let p = Timespec::new(2, 0);
+ let q = Timespec::new(1, 2);
+ let r = p - q;
+ assert_eq!(r.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 - 2));
+
+ let u = Timespec::new(1, 2);
+ let v = Timespec::new(2, 3);
+ let w = u - v;
+ assert_eq!(w.num_nanoseconds(), Some(-super::NSEC_PER_SEC as i64 - 1));
+ }
+
+ #[test]
+ fn test_time_sub() {
+ let a = ::now();
+ let b = at(a.to_timespec() + Duration::seconds(5));
+ let c = b - a;
+ assert_eq!(c.num_nanoseconds(), Some(super::NSEC_PER_SEC as i64 * 5));
+ }
+
+ #[test]
+ fn test_steadytime_sub() {
+ let a = SteadyTime::now();
+ let b = a + Duration::seconds(1);
+ assert_eq!(b - a, Duration::seconds(1));
+ assert_eq!(a - b, Duration::seconds(-1));
+ }
+
+ #[test]
+ fn test_date_before_1970() {
+ let early = strptime("1901-01-06", "%F").unwrap();
+ let late = strptime("2000-01-01", "%F").unwrap();
+ assert!(early < late);
+ }
+
+ #[test]
+ fn test_dst() {
+ let _reset = set_time_zone_london_dst();
+ let utc_in_feb = strptime("2015-02-01Z", "%F%z").unwrap();
+ let utc_in_jun = strptime("2015-06-01Z", "%F%z").unwrap();
+ let utc_in_nov = strptime("2015-11-01Z", "%F%z").unwrap();
+ let local_in_feb = utc_in_feb.to_local();
+ let local_in_jun = utc_in_jun.to_local();
+ let local_in_nov = utc_in_nov.to_local();
+
+ assert_eq!(local_in_feb.tm_mon, 1);
+ assert_eq!(local_in_feb.tm_hour, 0);
+ assert_eq!(local_in_feb.tm_utcoff, 0);
+ assert_eq!(local_in_feb.tm_isdst, 0);
+
+ assert_eq!(local_in_jun.tm_mon, 5);
+ assert_eq!(local_in_jun.tm_hour, 1);
+ assert_eq!(local_in_jun.tm_utcoff, 3600);
+ assert_eq!(local_in_jun.tm_isdst, 1);
+
+ assert_eq!(local_in_nov.tm_mon, 10);
+ assert_eq!(local_in_nov.tm_hour, 0);
+ assert_eq!(local_in_nov.tm_utcoff, 0);
+ assert_eq!(local_in_nov.tm_isdst, 0)
+ }
+}
diff --git a/third_party/rust/time-0.1.45/src/parse.rs b/third_party/rust/time-0.1.45/src/parse.rs
new file mode 100644
index 0000000000..602bdc5c76
--- /dev/null
+++ b/third_party/rust/time-0.1.45/src/parse.rs
@@ -0,0 +1,395 @@
+use super::{Timespec, Tm, at_utc, ParseError, NSEC_PER_SEC};
+
+/// Parses the time from the string according to the format string.
+pub fn strptime(mut s: &str, format: &str) -> Result<Tm, ParseError> {
+ let mut tm = Tm {
+ tm_sec: 0,
+ tm_min: 0,
+ tm_hour: 0,
+ tm_mday: 0,
+ tm_mon: 0,
+ tm_year: 0,
+ tm_wday: 0,
+ tm_yday: 0,
+ tm_isdst: 0,
+ tm_utcoff: 0,
+ tm_nsec: 0,
+ };
+ let mut chars = format.chars();
+
+ while let Some(ch) = chars.next() {
+ if ch == '%' {
+ if let Some(ch) = chars.next() {
+ parse_type(&mut s, ch, &mut tm)?;
+ }
+ } else {
+ parse_char(&mut s, ch)?;
+ }
+ }
+
+ Ok(tm)
+}
+
+fn parse_type(s: &mut &str, ch: char, tm: &mut Tm) -> Result<(), ParseError> {
+ match ch {
+ 'A' => match match_strs(s, &[("Sunday", 0),
+ ("Monday", 1),
+ ("Tuesday", 2),
+ ("Wednesday", 3),
+ ("Thursday", 4),
+ ("Friday", 5),
+ ("Saturday", 6)]) {
+ Some(v) => { tm.tm_wday = v; Ok(()) }
+ None => Err(ParseError::InvalidDay)
+ },
+ 'a' => match match_strs(s, &[("Sun", 0),
+ ("Mon", 1),
+ ("Tue", 2),
+ ("Wed", 3),
+ ("Thu", 4),
+ ("Fri", 5),
+ ("Sat", 6)]) {
+ Some(v) => { tm.tm_wday = v; Ok(()) }
+ None => Err(ParseError::InvalidDay)
+ },
+ 'B' => match match_strs(s, &[("January", 0),
+ ("February", 1),
+ ("March", 2),
+ ("April", 3),
+ ("May", 4),
+ ("June", 5),
+ ("July", 6),
+ ("August", 7),
+ ("September", 8),
+ ("October", 9),
+ ("November", 10),
+ ("December", 11)]) {
+ Some(v) => { tm.tm_mon = v; Ok(()) }
+ None => Err(ParseError::InvalidMonth)
+ },
+ 'b' | 'h' => match match_strs(s, &[("Jan", 0),
+ ("Feb", 1),
+ ("Mar", 2),
+ ("Apr", 3),
+ ("May", 4),
+ ("Jun", 5),
+ ("Jul", 6),
+ ("Aug", 7),
+ ("Sep", 8),
+ ("Oct", 9),
+ ("Nov", 10),
+ ("Dec", 11)]) {
+ Some(v) => { tm.tm_mon = v; Ok(()) }
+ None => Err(ParseError::InvalidMonth)
+ },
+ 'C' => match match_digits_in_range(s, 1, 2, false, 0, 99) {
+ Some(v) => { tm.tm_year += (v * 100) - 1900; Ok(()) }
+ None => Err(ParseError::InvalidYear)
+ },
+ 'c' => {
+ parse_type(s, 'a', tm)
+ .and_then(|()| parse_char(s, ' '))
+ .and_then(|()| parse_type(s, 'b', tm))
+ .and_then(|()| parse_char(s, ' '))
+ .and_then(|()| parse_type(s, 'e', tm))
+ .and_then(|()| parse_char(s, ' '))
+ .and_then(|()| parse_type(s, 'T', tm))
+ .and_then(|()| parse_char(s, ' '))
+ .and_then(|()| parse_type(s, 'Y', tm))
+ }
+ 'D' | 'x' => {
+ parse_type(s, 'm', tm)
+ .and_then(|()| parse_char(s, '/'))
+ .and_then(|()| parse_type(s, 'd', tm))
+ .and_then(|()| parse_char(s, '/'))
+ .and_then(|()| parse_type(s, 'y', tm))
+ }
+ 'd' => match match_digits_in_range(s, 1, 2, false, 1, 31) {
+ Some(v) => { tm.tm_mday = v; Ok(()) }
+ None => Err(ParseError::InvalidDayOfMonth)
+ },
+ 'e' => match match_digits_in_range(s, 1, 2, true, 1, 31) {
+ Some(v) => { tm.tm_mday = v; Ok(()) }
+ None => Err(ParseError::InvalidDayOfMonth)
+ },
+ 'f' => {
+ tm.tm_nsec = match_fractional_seconds(s);
+ Ok(())
+ }
+ 'F' => {
+ parse_type(s, 'Y', tm)
+ .and_then(|()| parse_char(s, '-'))
+ .and_then(|()| parse_type(s, 'm', tm))
+ .and_then(|()| parse_char(s, '-'))
+ .and_then(|()| parse_type(s, 'd', tm))
+ }
+ 'H' => {
+ match match_digits_in_range(s, 1, 2, false, 0, 23) {
+ Some(v) => { tm.tm_hour = v; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ }
+ }
+ 'I' => {
+ match match_digits_in_range(s, 1, 2, false, 1, 12) {
+ Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ }
+ }
+ 'j' => {
+ match match_digits_in_range(s, 1, 3, false, 1, 366) {
+ Some(v) => { tm.tm_yday = v - 1; Ok(()) }
+ None => Err(ParseError::InvalidDayOfYear)
+ }
+ }
+ 'k' => {
+ match match_digits_in_range(s, 1, 2, true, 0, 23) {
+ Some(v) => { tm.tm_hour = v; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ }
+ }
+ 'l' => {
+ match match_digits_in_range(s, 1, 2, true, 1, 12) {
+ Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ }
+ }
+ 'M' => {
+ match match_digits_in_range(s, 1, 2, false, 0, 59) {
+ Some(v) => { tm.tm_min = v; Ok(()) }
+ None => Err(ParseError::InvalidMinute)
+ }
+ }
+ 'm' => {
+ match match_digits_in_range(s, 1, 2, false, 1, 12) {
+ Some(v) => { tm.tm_mon = v - 1; Ok(()) }
+ None => Err(ParseError::InvalidMonth)
+ }
+ }
+ 'n' => parse_char(s, '\n'),
+ 'P' => match match_strs(s, &[("am", 0), ("pm", 12)]) {
+ Some(v) => { tm.tm_hour += v; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ },
+ 'p' => match match_strs(s, &[("AM", 0), ("PM", 12)]) {
+ Some(v) => { tm.tm_hour += v; Ok(()) }
+ None => Err(ParseError::InvalidHour)
+ },
+ 'R' => {
+ parse_type(s, 'H', tm)
+ .and_then(|()| parse_char(s, ':'))
+ .and_then(|()| parse_type(s, 'M', tm))
+ }
+ 'r' => {
+ parse_type(s, 'I', tm)
+ .and_then(|()| parse_char(s, ':'))
+ .and_then(|()| parse_type(s, 'M', tm))
+ .and_then(|()| parse_char(s, ':'))
+ .and_then(|()| parse_type(s, 'S', tm))
+ .and_then(|()| parse_char(s, ' '))
+ .and_then(|()| parse_type(s, 'p', tm))
+ }
+ 's' => {
+ match match_digits_i64(s, 1, 18, false) {
+ Some(v) => {
+ *tm = at_utc(Timespec::new(v, 0));
+ Ok(())
+ },
+ None => Err(ParseError::InvalidSecondsSinceEpoch)
+ }
+ }
+ 'S' => {
+ match match_digits_in_range(s, 1, 2, false, 0, 60) {
+ Some(v) => { tm.tm_sec = v; Ok(()) }
+ None => Err(ParseError::InvalidSecond)
+ }
+ }
+ //'s' {}
+ 'T' | 'X' => {
+ parse_type(s, 'H', tm)
+ .and_then(|()| parse_char(s, ':'))
+ .and_then(|()| parse_type(s, 'M', tm))
+ .and_then(|()| parse_char(s, ':'))
+ .and_then(|()| parse_type(s, 'S', tm))
+ }
+ 't' => parse_char(s, '\t'),
+ 'u' => {
+ match match_digits_in_range(s, 1, 1, false, 1, 7) {
+ Some(v) => { tm.tm_wday = if v == 7 { 0 } else { v }; Ok(()) }
+ None => Err(ParseError::InvalidDayOfWeek)
+ }
+ }
+ 'v' => {
+ parse_type(s, 'e', tm)
+ .and_then(|()| parse_char(s, '-'))
+ .and_then(|()| parse_type(s, 'b', tm))
+ .and_then(|()| parse_char(s, '-'))
+ .and_then(|()| parse_type(s, 'Y', tm))
+ }
+ //'W' {}
+ 'w' => {
+ match match_digits_in_range(s, 1, 1, false, 0, 6) {
+ Some(v) => { tm.tm_wday = v; Ok(()) }
+ None => Err(ParseError::InvalidDayOfWeek)
+ }
+ }
+ 'Y' => {
+ match match_digits(s, 4, 4, false) {
+ Some(v) => { tm.tm_year = v - 1900; Ok(()) }
+ None => Err(ParseError::InvalidYear)
+ }
+ }
+ 'y' => {
+ match match_digits_in_range(s, 1, 2, false, 0, 99) {
+ Some(v) => { tm.tm_year = v; Ok(()) }
+ None => Err(ParseError::InvalidYear)
+ }
+ }
+ 'Z' => {
+ if match_str(s, "UTC") || match_str(s, "GMT") {
+ tm.tm_utcoff = 0;
+ Ok(())
+ } else {
+ // It's odd, but to maintain compatibility with c's
+ // strptime we ignore the timezone.
+ for (i, ch) in s.char_indices() {
+ if ch == ' ' {
+ *s = &s[i..];
+ return Ok(())
+ }
+ }
+ *s = "";
+ Ok(())
+ }
+ }
+ 'z' => {
+ if parse_char(s, 'Z').is_ok() {
+ tm.tm_utcoff = 0;
+ Ok(())
+ } else {
+ let sign = if parse_char(s, '+').is_ok() {1}
+ else if parse_char(s, '-').is_ok() {-1}
+ else { return Err(ParseError::InvalidZoneOffset) };
+
+ let hours;
+ let minutes;
+
+ match match_digits(s, 2, 2, false) {
+ Some(h) => hours = h,
+ None => return Err(ParseError::InvalidZoneOffset)
+ }
+
+ // consume the colon if its present,
+ // just ignore it otherwise
+ let _ = parse_char(s, ':');
+
+ match match_digits(s, 2, 2, false) {
+ Some(m) => minutes = m,
+ None => return Err(ParseError::InvalidZoneOffset)
+ }
+
+ tm.tm_utcoff = sign * (hours * 60 * 60 + minutes * 60);
+ Ok(())
+ }
+ }
+ '%' => parse_char(s, '%'),
+ ch => Err(ParseError::InvalidFormatSpecifier(ch))
+ }
+}
+
+
+fn match_str(s: &mut &str, needle: &str) -> bool {
+ if s.starts_with(needle) {
+ *s = &s[needle.len()..];
+ true
+ } else {
+ false
+ }
+}
+
+fn match_strs(ss: &mut &str, strs: &[(&str, i32)]) -> Option<i32> {
+ for &(needle, value) in strs.iter() {
+ if match_str(ss, needle) {
+ return Some(value)
+ }
+ }
+ None
+}
+
+fn match_digits(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i32> {
+ match match_digits_i64(ss, min_digits, max_digits, ws) {
+ Some(v) => Some(v as i32),
+ None => None
+ }
+}
+
+fn match_digits_i64(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i64> {
+ let mut value : i64 = 0;
+ let mut n = 0;
+ if ws {
+ #[allow(deprecated)] // use `trim_start_matches` starting in 1.30
+ let s2 = ss.trim_left_matches(" ");
+ n = ss.len() - s2.len();
+ if n > max_digits { return None }
+ }
+ let chars = ss[n..].char_indices();
+ for (_, ch) in chars.take(max_digits - n) {
+ match ch {
+ '0' ... '9' => value = value * 10 + (ch as i64 - '0' as i64),
+ _ => break,
+ }
+ n += 1;
+ }
+
+ if n >= min_digits && n <= max_digits {
+ *ss = &ss[n..];
+ Some(value)
+ } else {
+ None
+ }
+}
+
+fn match_fractional_seconds(ss: &mut &str) -> i32 {
+ let mut value = 0;
+ let mut multiplier = NSEC_PER_SEC / 10;
+
+ let mut chars = ss.char_indices();
+ let orig = *ss;
+ for (i, ch) in &mut chars {
+ *ss = &orig[i..];
+ match ch {
+ '0' ... '9' => {
+ // This will drop digits after the nanoseconds place
+ let digit = ch as i32 - '0' as i32;
+ value += digit * multiplier;
+ multiplier /= 10;
+ }
+ _ => break
+ }
+ }
+
+ value
+}
+
+fn match_digits_in_range(ss: &mut &str,
+ min_digits : usize, max_digits : usize,
+ ws: bool, min: i32, max: i32) -> Option<i32> {
+ let before = *ss;
+ match match_digits(ss, min_digits, max_digits, ws) {
+ Some(val) if val >= min && val <= max => Some(val),
+ _ => { *ss = before; None }
+ }
+}
+
+fn parse_char(s: &mut &str, c: char) -> Result<(), ParseError> {
+ match s.char_indices().next() {
+ Some((i, c2)) => {
+ if c == c2 {
+ *s = &s[i + c2.len_utf8()..];
+ Ok(())
+ } else {
+ Err(ParseError::UnexpectedCharacter(c, c2))
+ }
+ }
+ None => Err(ParseError::InvalidTime),
+ }
+}
diff --git a/third_party/rust/time-0.1.45/src/sys.rs b/third_party/rust/time-0.1.45/src/sys.rs
new file mode 100644
index 0000000000..c5765ca895
--- /dev/null
+++ b/third_party/rust/time-0.1.45/src/sys.rs
@@ -0,0 +1,996 @@
+#![allow(bad_style)]
+
+pub use self::inner::*;
+
+#[cfg(any(
+ all(target_arch = "wasm32", not(target_os = "emscripten")),
+ target_env = "sgx"
+))]
+mod common {
+ use Tm;
+
+ pub fn time_to_tm(ts: i64, tm: &mut Tm) {
+ let leapyear = |year| -> bool {
+ year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+ };
+
+ static _ytab: [[i64; 12]; 2] = [
+ [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ],
+ [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
+ ];
+
+ let mut year = 1970;
+
+ let dayclock = ts % 86400;
+ let mut dayno = ts / 86400;
+
+ tm.tm_sec = (dayclock % 60) as i32;
+ tm.tm_min = ((dayclock % 3600) / 60) as i32;
+ tm.tm_hour = (dayclock / 3600) as i32;
+ tm.tm_wday = ((dayno + 4) % 7) as i32;
+ loop {
+ let yearsize = if leapyear(year) {
+ 366
+ } else {
+ 365
+ };
+ if dayno >= yearsize {
+ dayno -= yearsize;
+ year += 1;
+ } else {
+ break;
+ }
+ }
+ tm.tm_year = (year - 1900) as i32;
+ tm.tm_yday = dayno as i32;
+ let mut mon = 0;
+ while dayno >= _ytab[if leapyear(year) { 1 } else { 0 }][mon] {
+ dayno -= _ytab[if leapyear(year) { 1 } else { 0 }][mon];
+ mon += 1;
+ }
+ tm.tm_mon = mon as i32;
+ tm.tm_mday = dayno as i32 + 1;
+ tm.tm_isdst = 0;
+ }
+
+ pub fn tm_to_time(tm: &Tm) -> i64 {
+ let mut y = tm.tm_year as i64 + 1900;
+ let mut m = tm.tm_mon as i64 + 1;
+ if m <= 2 {
+ y -= 1;
+ m += 12;
+ }
+ let d = tm.tm_mday as i64;
+ let h = tm.tm_hour as i64;
+ let mi = tm.tm_min as i64;
+ let s = tm.tm_sec as i64;
+ (365*y + y/4 - y/100 + y/400 + 3*(m+1)/5 + 30*m + d - 719561)
+ * 86400 + 3600 * h + 60 * mi + s
+ }
+}
+
+#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
+mod inner {
+ use std::ops::{Add, Sub};
+ use Tm;
+ use Duration;
+ use super::common::{time_to_tm, tm_to_time};
+
+ #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+ pub struct SteadyTime;
+
+ pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) {
+ time_to_tm(sec, tm);
+ }
+
+ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
+ // FIXME: Add timezone logic
+ time_to_tm(sec, tm);
+ }
+
+ pub fn utc_tm_to_time(tm: &Tm) -> i64 {
+ tm_to_time(tm)
+ }
+
+ pub fn local_tm_to_time(tm: &Tm) -> i64 {
+ // FIXME: Add timezone logic
+ tm_to_time(tm)
+ }
+
+ pub fn get_time() -> (i64, i32) {
+ unimplemented!()
+ }
+
+ pub fn get_precise_ns() -> u64 {
+ unimplemented!()
+ }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ unimplemented!()
+ }
+ }
+
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, _other: SteadyTime) -> Duration {
+ unimplemented!()
+ }
+ }
+
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, _other: Duration) -> SteadyTime {
+ unimplemented!()
+ }
+ }
+
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(self, _other: Duration) -> SteadyTime {
+ unimplemented!()
+ }
+ }
+}
+
+#[cfg(target_os = "wasi")]
+mod inner {
+ use std::ops::{Add, Sub};
+ use Tm;
+ use Duration;
+ use super::common::{time_to_tm, tm_to_time};
+ use wasi::{clock_time_get, CLOCKID_MONOTONIC, CLOCKID_REALTIME};
+
+ #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+ pub struct SteadyTime {
+ t: u64
+ }
+
+ pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) {
+ time_to_tm(sec, tm);
+ }
+
+ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
+ // FIXME: Add timezone logic
+ time_to_tm(sec, tm);
+ }
+
+ pub fn utc_tm_to_time(tm: &Tm) -> i64 {
+ tm_to_time(tm)
+ }
+
+ pub fn local_tm_to_time(tm: &Tm) -> i64 {
+ // FIXME: Add timezone logic
+ tm_to_time(tm)
+ }
+
+ pub fn get_time() -> (i64, i32) {
+ let ts = get_precise_ns();
+ (
+ ts as i64 / 1_000_000_000,
+ (ts as i64 % 1_000_000_000) as i32,
+ )
+ }
+
+ pub fn get_precise_ns() -> u64 {
+ unsafe { clock_time_get(CLOCKID_REALTIME, 1_000_000_000) }
+ .expect("Host doesn't implement a real-time clock")
+ }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ SteadyTime {
+ t: unsafe { clock_time_get(CLOCKID_MONOTONIC, 1_000_000_000) }
+ .expect("Host doesn't implement a monotonic clock"),
+ }
+ }
+ }
+
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, other: SteadyTime) -> Duration {
+ Duration::nanoseconds(self.t as i64 - other.t as i64)
+ }
+ }
+
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, other: Duration) -> SteadyTime {
+ self + -other
+ }
+ }
+
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(self, other: Duration) -> SteadyTime {
+ let delta = other.num_nanoseconds().unwrap();
+ SteadyTime {
+ t: (self.t as i64 + delta) as u64,
+ }
+ }
+ }
+}
+
+#[cfg(target_env = "sgx")]
+mod inner {
+ use std::ops::{Add, Sub};
+ use Tm;
+ use Duration;
+ use super::common::{time_to_tm, tm_to_time};
+ use std::time::SystemTime;
+
+ /// The number of nanoseconds in seconds.
+ const NANOS_PER_SEC: u64 = 1_000_000_000;
+
+ #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+ pub struct SteadyTime {
+ t: Duration
+ }
+
+ pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) {
+ time_to_tm(sec, tm);
+ }
+
+ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
+ // FIXME: Add timezone logic
+ time_to_tm(sec, tm);
+ }
+
+ pub fn utc_tm_to_time(tm: &Tm) -> i64 {
+ tm_to_time(tm)
+ }
+
+ pub fn local_tm_to_time(tm: &Tm) -> i64 {
+ // FIXME: Add timezone logic
+ tm_to_time(tm)
+ }
+
+ pub fn get_time() -> (i64, i32) {
+ SteadyTime::now().t.raw()
+ }
+
+ pub fn get_precise_ns() -> u64 {
+ // This unwrap is safe because current time is well ahead of UNIX_EPOCH, unless system
+ // clock is adjusted backward.
+ let std_duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ std_duration.as_secs() * NANOS_PER_SEC + std_duration.subsec_nanos() as u64
+ }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ // This unwrap is safe because current time is well ahead of UNIX_EPOCH, unless system
+ // clock is adjusted backward.
+ let std_duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ // This unwrap is safe because duration is well within the limits of i64.
+ let duration = Duration::from_std(std_duration).unwrap();
+ SteadyTime { t: duration }
+ }
+ }
+
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, other: SteadyTime) -> Duration {
+ self.t - other.t
+ }
+ }
+
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, other: Duration) -> SteadyTime {
+ SteadyTime { t: self.t - other }
+ }
+ }
+
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(self, other: Duration) -> SteadyTime {
+ SteadyTime { t: self.t + other }
+ }
+ }
+}
+
+#[cfg(unix)]
+mod inner {
+ use libc::{self, time_t};
+ use std::mem;
+ use std::io;
+ use Tm;
+
+ #[cfg(any(target_os = "macos", target_os = "ios"))]
+ pub use self::mac::*;
+ #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))]
+ pub use self::unix::*;
+
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ extern {
+ static timezone: time_t;
+ static altzone: time_t;
+ }
+
+ fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) {
+ tm.tm_sec = rust_tm.tm_sec;
+ tm.tm_min = rust_tm.tm_min;
+ tm.tm_hour = rust_tm.tm_hour;
+ tm.tm_mday = rust_tm.tm_mday;
+ tm.tm_mon = rust_tm.tm_mon;
+ tm.tm_year = rust_tm.tm_year;
+ tm.tm_wday = rust_tm.tm_wday;
+ tm.tm_yday = rust_tm.tm_yday;
+ tm.tm_isdst = rust_tm.tm_isdst;
+ }
+
+ fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) {
+ rust_tm.tm_sec = tm.tm_sec;
+ rust_tm.tm_min = tm.tm_min;
+ rust_tm.tm_hour = tm.tm_hour;
+ rust_tm.tm_mday = tm.tm_mday;
+ rust_tm.tm_mon = tm.tm_mon;
+ rust_tm.tm_year = tm.tm_year;
+ rust_tm.tm_wday = tm.tm_wday;
+ rust_tm.tm_yday = tm.tm_yday;
+ rust_tm.tm_isdst = tm.tm_isdst;
+ rust_tm.tm_utcoff = utcoff;
+ }
+
+ #[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))]
+ unsafe fn timegm(tm: *mut libc::tm) -> time_t {
+ use std::env::{set_var, var_os, remove_var};
+ extern {
+ fn tzset();
+ }
+
+ let ret;
+
+ let current_tz = var_os("TZ");
+ set_var("TZ", "UTC");
+ tzset();
+
+ ret = libc::mktime(tm);
+
+ if let Some(tz) = current_tz {
+ set_var("TZ", tz);
+ } else {
+ remove_var("TZ");
+ }
+ tzset();
+
+ ret
+ }
+
+ pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) {
+ unsafe {
+ let sec = sec as time_t;
+ let mut out = mem::zeroed();
+ if libc::gmtime_r(&sec, &mut out).is_null() {
+ panic!("gmtime_r failed: {}", io::Error::last_os_error());
+ }
+ tm_to_rust_tm(&out, 0, tm);
+ }
+ }
+
+ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
+ unsafe {
+ let sec = sec as time_t;
+ let mut out = mem::zeroed();
+ if libc::localtime_r(&sec, &mut out).is_null() {
+ panic!("localtime_r failed: {}", io::Error::last_os_error());
+ }
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ let gmtoff = {
+ ::tzset();
+ // < 0 means we don't know; assume we're not in DST.
+ if out.tm_isdst == 0 {
+ // timezone is seconds west of UTC, tm_gmtoff is seconds east
+ -timezone
+ } else if out.tm_isdst > 0 {
+ -altzone
+ } else {
+ -timezone
+ }
+ };
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ let gmtoff = out.tm_gmtoff;
+ tm_to_rust_tm(&out, gmtoff as i32, tm);
+ }
+ }
+
+ pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 {
+ #[cfg(all(target_os = "android", target_pointer_width = "32"))]
+ use libc::timegm64 as timegm;
+ #[cfg(not(any(
+ all(target_os = "android", target_pointer_width = "32"),
+ target_os = "nacl",
+ target_os = "solaris",
+ target_os = "illumos"
+ )))]
+ use libc::timegm;
+
+ let mut tm = unsafe { mem::zeroed() };
+ rust_tm_to_tm(rust_tm, &mut tm);
+ unsafe { timegm(&mut tm) as i64 }
+ }
+
+ pub fn local_tm_to_time(rust_tm: &Tm) -> i64 {
+ let mut tm = unsafe { mem::zeroed() };
+ rust_tm_to_tm(rust_tm, &mut tm);
+ unsafe { libc::mktime(&mut tm) as i64 }
+ }
+
+ #[cfg(any(target_os = "macos", target_os = "ios"))]
+ mod mac {
+ #[allow(deprecated)]
+ use libc::{self, timeval, mach_timebase_info};
+ #[allow(deprecated)]
+ use std::sync::{Once, ONCE_INIT};
+ use std::ops::{Add, Sub};
+ use Duration;
+
+ #[allow(deprecated)]
+ fn info() -> &'static mach_timebase_info {
+ static mut INFO: mach_timebase_info = mach_timebase_info {
+ numer: 0,
+ denom: 0,
+ };
+ static ONCE: Once = ONCE_INIT;
+
+ unsafe {
+ ONCE.call_once(|| {
+ mach_timebase_info(&mut INFO);
+ });
+ &INFO
+ }
+ }
+
+ pub fn get_time() -> (i64, i32) {
+ use std::ptr;
+ let mut tv = timeval { tv_sec: 0, tv_usec: 0 };
+ unsafe { libc::gettimeofday(&mut tv, ptr::null_mut()); }
+ (tv.tv_sec as i64, tv.tv_usec * 1000)
+ }
+
+ #[allow(deprecated)]
+ #[inline]
+ pub fn get_precise_ns() -> u64 {
+ unsafe {
+ let time = libc::mach_absolute_time();
+ let info = info();
+ time * info.numer as u64 / info.denom as u64
+ }
+ }
+
+ #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
+ pub struct SteadyTime { t: u64 }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ SteadyTime { t: get_precise_ns() }
+ }
+ }
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, other: SteadyTime) -> Duration {
+ Duration::nanoseconds(self.t as i64 - other.t as i64)
+ }
+ }
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, other: Duration) -> SteadyTime {
+ self + -other
+ }
+ }
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(self, other: Duration) -> SteadyTime {
+ let delta = other.num_nanoseconds().unwrap();
+ SteadyTime {
+ t: (self.t as i64 + delta) as u64
+ }
+ }
+ }
+ }
+
+ #[cfg(test)]
+ pub struct TzReset;
+
+ #[cfg(test)]
+ pub fn set_los_angeles_time_zone() -> TzReset {
+ use std::env;
+ env::set_var("TZ", "America/Los_Angeles");
+ ::tzset();
+ TzReset
+ }
+
+ #[cfg(test)]
+ pub fn set_london_with_dst_time_zone() -> TzReset {
+ use std::env;
+ env::set_var("TZ", "Europe/London");
+ ::tzset();
+ TzReset
+ }
+
+ #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))]
+ mod unix {
+ use std::fmt;
+ use std::cmp::Ordering;
+ use std::mem::zeroed;
+ use std::ops::{Add, Sub};
+ use libc;
+
+ use Duration;
+
+ pub fn get_time() -> (i64, i32) {
+ // SAFETY: libc::timespec is zero initializable.
+ let mut tv: libc::timespec = unsafe { zeroed() };
+ unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut tv); }
+ (tv.tv_sec as i64, tv.tv_nsec as i32)
+ }
+
+ pub fn get_precise_ns() -> u64 {
+ // SAFETY: libc::timespec is zero initializable.
+ let mut ts: libc::timespec = unsafe { zeroed() };
+ unsafe {
+ libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
+ }
+ (ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64)
+ }
+
+ #[derive(Copy)]
+ pub struct SteadyTime {
+ t: libc::timespec,
+ }
+
+ impl fmt::Debug for SteadyTime {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(fmt, "SteadyTime {{ tv_sec: {:?}, tv_nsec: {:?} }}",
+ self.t.tv_sec, self.t.tv_nsec)
+ }
+ }
+
+ impl Clone for SteadyTime {
+ fn clone(&self) -> SteadyTime {
+ SteadyTime { t: self.t }
+ }
+ }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ let mut t = SteadyTime {
+ // SAFETY: libc::timespec is zero initializable.
+ t: unsafe { zeroed() }
+ };
+ unsafe {
+ assert_eq!(0, libc::clock_gettime(libc::CLOCK_MONOTONIC,
+ &mut t.t));
+ }
+ t
+ }
+ }
+
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, other: SteadyTime) -> Duration {
+ if self.t.tv_nsec >= other.t.tv_nsec {
+ Duration::seconds(self.t.tv_sec as i64 - other.t.tv_sec as i64) +
+ Duration::nanoseconds(self.t.tv_nsec as i64 - other.t.tv_nsec as i64)
+ } else {
+ Duration::seconds(self.t.tv_sec as i64 - 1 - other.t.tv_sec as i64) +
+ Duration::nanoseconds(self.t.tv_nsec as i64 + ::NSEC_PER_SEC as i64 -
+ other.t.tv_nsec as i64)
+ }
+ }
+ }
+
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, other: Duration) -> SteadyTime {
+ self + -other
+ }
+ }
+
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(mut self, other: Duration) -> SteadyTime {
+ let seconds = other.num_seconds();
+ let nanoseconds = other - Duration::seconds(seconds);
+ let nanoseconds = nanoseconds.num_nanoseconds().unwrap();
+
+ #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
+ type nsec = i64;
+ #[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
+ type nsec = libc::c_long;
+
+ self.t.tv_sec += seconds as libc::time_t;
+ self.t.tv_nsec += nanoseconds as nsec;
+ if self.t.tv_nsec >= ::NSEC_PER_SEC as nsec {
+ self.t.tv_nsec -= ::NSEC_PER_SEC as nsec;
+ self.t.tv_sec += 1;
+ } else if self.t.tv_nsec < 0 {
+ self.t.tv_sec -= 1;
+ self.t.tv_nsec += ::NSEC_PER_SEC as nsec;
+ }
+ self
+ }
+ }
+
+ impl PartialOrd for SteadyTime {
+ fn partial_cmp(&self, other: &SteadyTime) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+ }
+
+ impl Ord for SteadyTime {
+ fn cmp(&self, other: &SteadyTime) -> Ordering {
+ match self.t.tv_sec.cmp(&other.t.tv_sec) {
+ Ordering::Equal => self.t.tv_nsec.cmp(&other.t.tv_nsec),
+ ord => ord
+ }
+ }
+ }
+
+ impl PartialEq for SteadyTime {
+ fn eq(&self, other: &SteadyTime) -> bool {
+ self.t.tv_sec == other.t.tv_sec &&
+ self.t.tv_nsec == other.t.tv_nsec
+ }
+ }
+
+ impl Eq for SteadyTime {}
+
+ }
+}
+
+#[cfg(windows)]
+#[allow(non_snake_case)]
+mod inner {
+ use std::io;
+ use std::mem;
+ #[allow(deprecated)]
+ use std::sync::{Once, ONCE_INIT};
+ use std::ops::{Add, Sub};
+ use {Tm, Duration};
+
+ use winapi::um::winnt::*;
+ use winapi::shared::minwindef::*;
+ use winapi::um::minwinbase::SYSTEMTIME;
+ use winapi::um::profileapi::*;
+ use winapi::um::timezoneapi::*;
+ use winapi::um::sysinfoapi::GetSystemTimeAsFileTime;
+
+ fn frequency() -> i64 {
+ static mut FREQUENCY: i64 = 0;
+ #[allow(deprecated)]
+ static ONCE: Once = ONCE_INIT;
+
+ unsafe {
+ ONCE.call_once(|| {
+ let mut l = i64_to_large_integer(0);
+ QueryPerformanceFrequency(&mut l);
+ FREQUENCY = large_integer_to_i64(l);
+ });
+ FREQUENCY
+ }
+ }
+
+ fn i64_to_large_integer(i: i64) -> LARGE_INTEGER {
+ unsafe {
+ let mut large_integer: LARGE_INTEGER = mem::zeroed();
+ *large_integer.QuadPart_mut() = i;
+ large_integer
+ }
+ }
+
+ fn large_integer_to_i64(l: LARGE_INTEGER) -> i64 {
+ unsafe {
+ *l.QuadPart()
+ }
+ }
+
+ const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
+ const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
+
+ fn time_to_file_time(sec: i64) -> FILETIME {
+ let t = (((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH)) as u64;
+ FILETIME {
+ dwLowDateTime: t as DWORD,
+ dwHighDateTime: (t >> 32) as DWORD
+ }
+ }
+
+ fn file_time_as_u64(ft: &FILETIME) -> u64 {
+ ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64)
+ }
+
+ fn file_time_to_nsec(ft: &FILETIME) -> i32 {
+ let t = file_time_as_u64(ft) as i64;
+ ((t % HECTONANOSECS_IN_SEC) * 100) as i32
+ }
+
+ fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 {
+ let t = file_time_as_u64(ft) as i64;
+ ((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64
+ }
+
+ fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME {
+ unsafe {
+ let mut ft = mem::zeroed();
+ SystemTimeToFileTime(sys, &mut ft);
+ ft
+ }
+ }
+
+ fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME {
+ let mut sys: SYSTEMTIME = unsafe { mem::zeroed() };
+ sys.wSecond = tm.tm_sec as WORD;
+ sys.wMinute = tm.tm_min as WORD;
+ sys.wHour = tm.tm_hour as WORD;
+ sys.wDay = tm.tm_mday as WORD;
+ sys.wDayOfWeek = tm.tm_wday as WORD;
+ sys.wMonth = (tm.tm_mon + 1) as WORD;
+ sys.wYear = (tm.tm_year + 1900) as WORD;
+ sys
+ }
+
+ fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) {
+ tm.tm_sec = sys.wSecond as i32;
+ tm.tm_min = sys.wMinute as i32;
+ tm.tm_hour = sys.wHour as i32;
+ tm.tm_mday = sys.wDay as i32;
+ tm.tm_wday = sys.wDayOfWeek as i32;
+ tm.tm_mon = (sys.wMonth - 1) as i32;
+ tm.tm_year = (sys.wYear - 1900) as i32;
+ tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
+
+ fn yday(year: i32, month: i32, day: i32) -> i32 {
+ let leap = if month > 2 {
+ if year % 4 == 0 { 1 } else { 2 }
+ } else {
+ 0
+ };
+ let july = if month > 7 { 1 } else { 0 };
+
+ (month - 1) * 30 + month / 2 + (day - 1) - leap + july
+ }
+ }
+
+ macro_rules! call {
+ ($name:ident($($arg:expr),*)) => {
+ if $name($($arg),*) == 0 {
+ panic!(concat!(stringify!($name), " failed with: {}"),
+ io::Error::last_os_error());
+ }
+ }
+ }
+
+ pub fn time_to_utc_tm(sec: i64, tm: &mut Tm) {
+ let mut out = unsafe { mem::zeroed() };
+ let ft = time_to_file_time(sec);
+ unsafe {
+ call!(FileTimeToSystemTime(&ft, &mut out));
+ }
+ system_time_to_tm(&out, tm);
+ tm.tm_utcoff = 0;
+ }
+
+ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) {
+ let ft = time_to_file_time(sec);
+ unsafe {
+ let mut utc = mem::zeroed();
+ let mut local = mem::zeroed();
+ call!(FileTimeToSystemTime(&ft, &mut utc));
+ call!(SystemTimeToTzSpecificLocalTime(0 as *const _,
+ &mut utc, &mut local));
+ system_time_to_tm(&local, tm);
+
+ let local = system_time_to_file_time(&local);
+ let local_sec = file_time_to_unix_seconds(&local);
+
+ let mut tz = mem::zeroed();
+ GetTimeZoneInformation(&mut tz);
+
+ // SystemTimeToTzSpecificLocalTime already applied the biases so
+ // check if it non standard
+ tm.tm_utcoff = (local_sec - sec) as i32;
+ tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) {
+ 0
+ } else {
+ 1
+ };
+ }
+ }
+
+ pub fn utc_tm_to_time(tm: &Tm) -> i64 {
+ unsafe {
+ let mut ft = mem::zeroed();
+ let sys_time = tm_to_system_time(tm);
+ call!(SystemTimeToFileTime(&sys_time, &mut ft));
+ file_time_to_unix_seconds(&ft)
+ }
+ }
+
+ pub fn local_tm_to_time(tm: &Tm) -> i64 {
+ unsafe {
+ let mut ft = mem::zeroed();
+ let mut utc = mem::zeroed();
+ let mut sys_time = tm_to_system_time(tm);
+ call!(TzSpecificLocalTimeToSystemTime(0 as *mut _,
+ &mut sys_time, &mut utc));
+ call!(SystemTimeToFileTime(&utc, &mut ft));
+ file_time_to_unix_seconds(&ft)
+ }
+ }
+
+ pub fn get_time() -> (i64, i32) {
+ unsafe {
+ let mut ft = mem::zeroed();
+ GetSystemTimeAsFileTime(&mut ft);
+ (file_time_to_unix_seconds(&ft), file_time_to_nsec(&ft))
+ }
+ }
+
+ pub fn get_precise_ns() -> u64 {
+ let mut ticks = i64_to_large_integer(0);
+ unsafe {
+ assert!(QueryPerformanceCounter(&mut ticks) == 1);
+ }
+ mul_div_i64(large_integer_to_i64(ticks), 1000000000, frequency()) as u64
+
+ }
+
+ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
+ pub struct SteadyTime {
+ t: i64,
+ }
+
+ impl SteadyTime {
+ pub fn now() -> SteadyTime {
+ let mut l = i64_to_large_integer(0);
+ unsafe { QueryPerformanceCounter(&mut l); }
+ SteadyTime { t : large_integer_to_i64(l) }
+ }
+ }
+
+ impl Sub for SteadyTime {
+ type Output = Duration;
+ fn sub(self, other: SteadyTime) -> Duration {
+ let diff = self.t as i64 - other.t as i64;
+ Duration::nanoseconds(mul_div_i64(diff, 1000000000,
+ frequency()))
+ }
+ }
+
+ impl Sub<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn sub(self, other: Duration) -> SteadyTime {
+ self + -other
+ }
+ }
+
+ impl Add<Duration> for SteadyTime {
+ type Output = SteadyTime;
+ fn add(mut self, other: Duration) -> SteadyTime {
+ self.t += (other.num_microseconds().unwrap() * frequency() /
+ 1_000_000) as i64;
+ self
+ }
+ }
+
+ #[cfg(test)]
+ pub struct TzReset {
+ old: TIME_ZONE_INFORMATION,
+ }
+
+ #[cfg(test)]
+ impl Drop for TzReset {
+ fn drop(&mut self) {
+ unsafe {
+ call!(SetTimeZoneInformation(&self.old));
+ }
+ }
+ }
+
+ #[cfg(test)]
+ pub fn set_los_angeles_time_zone() -> TzReset {
+ acquire_privileges();
+
+ unsafe {
+ let mut tz = mem::zeroed::<TIME_ZONE_INFORMATION>();
+ GetTimeZoneInformation(&mut tz);
+ let ret = TzReset { old: tz };
+ tz.Bias = 60 * 8;
+ call!(SetTimeZoneInformation(&tz));
+ return ret
+ }
+ }
+
+ #[cfg(test)]
+ pub fn set_london_with_dst_time_zone() -> TzReset {
+ acquire_privileges();
+
+ unsafe {
+ let mut tz = mem::zeroed::<TIME_ZONE_INFORMATION>();
+ GetTimeZoneInformation(&mut tz);
+ let ret = TzReset { old: tz };
+ // Since date set precisely this is 2015's dates
+ tz.Bias = 0;
+ tz.DaylightBias = -60;
+ tz.DaylightDate.wYear = 0;
+ tz.DaylightDate.wMonth = 3;
+ tz.DaylightDate.wDayOfWeek = 0;
+ tz.DaylightDate.wDay = 5;
+ tz.DaylightDate.wHour = 2;
+ tz.StandardBias = 0;
+ tz.StandardDate.wYear = 0;
+ tz.StandardDate.wMonth = 10;
+ tz.StandardDate.wDayOfWeek = 0;
+ tz.StandardDate.wDay = 5;
+ tz.StandardDate.wHour = 2;
+ call!(SetTimeZoneInformation(&tz));
+ return ret
+ }
+ }
+
+ // Ensures that this process has the necessary privileges to set a new time
+ // zone, and this is all transcribed from:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724944%28v=vs.85%29.aspx
+ #[cfg(test)]
+ fn acquire_privileges() {
+ use winapi::um::processthreadsapi::*;
+ use winapi::um::winbase::LookupPrivilegeValueA;
+ const SE_PRIVILEGE_ENABLED: DWORD = 2;
+ #[allow(deprecated)]
+ static INIT: Once = ONCE_INIT;
+
+ // TODO: FIXME
+ extern "system" {
+ fn AdjustTokenPrivileges(
+ TokenHandle: HANDLE, DisableAllPrivileges: BOOL, NewState: PTOKEN_PRIVILEGES,
+ BufferLength: DWORD, PreviousState: PTOKEN_PRIVILEGES, ReturnLength: PDWORD,
+ ) -> BOOL;
+ }
+
+ INIT.call_once(|| unsafe {
+ let mut hToken = 0 as *mut _;
+ call!(OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
+ &mut hToken));
+
+ let mut tkp = mem::zeroed::<TOKEN_PRIVILEGES>();
+ assert_eq!(tkp.Privileges.len(), 1);
+ let c = ::std::ffi::CString::new("SeTimeZonePrivilege").unwrap();
+ call!(LookupPrivilegeValueA(0 as *const _, c.as_ptr(),
+ &mut tkp.Privileges[0].Luid));
+ tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ tkp.PrivilegeCount = 1;
+ call!(AdjustTokenPrivileges(hToken, FALSE, &mut tkp, 0,
+ 0 as *mut _, 0 as *mut _));
+ });
+ }
+
+
+
+ // Computes (value*numer)/denom without overflow, as long as both
+ // (numer*denom) and the overall result fit into i64 (which is the case
+ // for our time conversions).
+ fn mul_div_i64(value: i64, numer: i64, denom: i64) -> i64 {
+ let q = value / denom;
+ let r = value % denom;
+ // Decompose value as (value/denom*denom + value%denom),
+ // substitute into (value*numer)/denom and simplify.
+ // r < denom, so (denom*numer) is the upper bound of (r*numer)
+ q * numer + r * numer / denom
+ }
+
+ #[test]
+ fn test_muldiv() {
+ assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000, 1_000_000),
+ 1_000_000_000_001_000);
+ assert_eq!(mul_div_i64(-1_000_000_000_001, 1_000_000_000, 1_000_000),
+ -1_000_000_000_001_000);
+ assert_eq!(mul_div_i64(-1_000_000_000_001,-1_000_000_000, 1_000_000),
+ 1_000_000_000_001_000);
+ assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000,-1_000_000),
+ -1_000_000_000_001_000);
+ assert_eq!(mul_div_i64( 1_000_000_000_001,-1_000_000_000,-1_000_000),
+ 1_000_000_000_001_000);
+ }
+}
diff --git a/third_party/rust/time-core/.cargo-checksum.json b/third_party/rust/time-core/.cargo-checksum.json
new file mode 100644
index 0000000000..e58090e197
--- /dev/null
+++ b/third_party/rust/time-core/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"d20a28a1ed2c35aff8a60f495a0ed440e022051d3909b372bf5e262fd62d7b29","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/lib.rs":"247b6ac4c2acc97e51552fd7ca1ef7cb2cbc30f8bcbbf5d029553a83a7fe2cc4","src/util.rs":"52c1fbf68b71c3582caf0d9a8255378c6c14a737e2df8d7e6d6603b0eb12ca06"},"package":"2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"} \ No newline at end of file
diff --git a/third_party/rust/time-core/Cargo.toml b/third_party/rust/time-core/Cargo.toml
new file mode 100644
index 0000000000..574a387470
--- /dev/null
+++ b/third_party/rust/time-core/Cargo.toml
@@ -0,0 +1,32 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.60.0"
+name = "time-core"
+version = "0.1.0"
+authors = [
+ "Jacob Pratt <open-source@jhpratt.dev>",
+ "Time contributors",
+]
+description = "This crate is an implementation detail and should not be relied upon directly."
+keywords = [
+ "date",
+ "time",
+ "calendar",
+ "duration",
+]
+categories = ["date-and-time"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/time-rs/time"
+
+[dependencies]
diff --git a/third_party/rust/time-core/LICENSE-Apache b/third_party/rust/time-core/LICENSE-Apache
new file mode 100644
index 0000000000..7646f21e37
--- /dev/null
+++ b/third_party/rust/time-core/LICENSE-Apache
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2022 Jacob Pratt et al.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/rust/time-core/LICENSE-MIT b/third_party/rust/time-core/LICENSE-MIT
new file mode 100644
index 0000000000..a11a755732
--- /dev/null
+++ b/third_party/rust/time-core/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright (c) 2022 Jacob Pratt et al.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/rust/time-core/src/lib.rs b/third_party/rust/time-core/src/lib.rs
new file mode 100644
index 0000000000..d962578b59
--- /dev/null
+++ b/third_party/rust/time-core/src/lib.rs
@@ -0,0 +1,52 @@
+//! Core items for `time`.
+//!
+//! This crate is an implementation detail of `time` and should not be relied upon directly.
+
+#![no_std]
+#![deny(
+ anonymous_parameters,
+ clippy::all,
+ clippy::alloc_instead_of_core,
+ clippy::explicit_auto_deref,
+ clippy::obfuscated_if_else,
+ clippy::std_instead_of_core,
+ clippy::undocumented_unsafe_blocks,
+ const_err,
+ illegal_floating_point_literal_pattern,
+ late_bound_lifetime_arguments,
+ path_statements,
+ patterns_in_fns_without_body,
+ rust_2018_idioms,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unsafe_op_in_unsafe_fn,
+ unused_extern_crates,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::private_intra_doc_links
+)]
+#![warn(
+ clippy::dbg_macro,
+ clippy::decimal_literal_representation,
+ clippy::get_unwrap,
+ clippy::missing_docs_in_private_items,
+ clippy::nursery,
+ clippy::print_stdout,
+ clippy::todo,
+ clippy::unimplemented,
+ clippy::unnested_or_patterns,
+ clippy::unwrap_in_result,
+ clippy::unwrap_used,
+ clippy::use_debug,
+ deprecated_in_future,
+ missing_copy_implementations,
+ missing_debug_implementations,
+ unused_qualifications,
+ variant_size_differences
+)]
+#![allow(clippy::redundant_pub_crate)]
+#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
+#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
+#![doc(test(attr(deny(warnings))))]
+
+pub mod util;
diff --git a/third_party/rust/time-core/src/util.rs b/third_party/rust/time-core/src/util.rs
new file mode 100644
index 0000000000..26ced0638c
--- /dev/null
+++ b/third_party/rust/time-core/src/util.rs
@@ -0,0 +1,52 @@
+//! Utility functions.
+
+/// Returns if the provided year is a leap year in the proleptic Gregorian calendar. Uses
+/// [astronomical year numbering](https://en.wikipedia.org/wiki/Astronomical_year_numbering).
+///
+/// ```rust
+/// # use time::util::is_leap_year;
+/// assert!(!is_leap_year(1900));
+/// assert!(is_leap_year(2000));
+/// assert!(is_leap_year(2004));
+/// assert!(!is_leap_year(2005));
+/// assert!(!is_leap_year(2100));
+/// ```
+pub const fn is_leap_year(year: i32) -> bool {
+ year % 4 == 0 && (year % 25 != 0 || year % 16 == 0)
+}
+
+/// Get the number of calendar days in a given year.
+///
+/// The returned value will always be either 365 or 366.
+///
+/// ```rust
+/// # use time::util::days_in_year;
+/// assert_eq!(days_in_year(1900), 365);
+/// assert_eq!(days_in_year(2000), 366);
+/// assert_eq!(days_in_year(2004), 366);
+/// assert_eq!(days_in_year(2005), 365);
+/// assert_eq!(days_in_year(2100), 365);
+/// ```
+pub const fn days_in_year(year: i32) -> u16 {
+ if is_leap_year(year) { 366 } else { 365 }
+}
+
+/// Get the number of weeks in the ISO year.
+///
+/// The returned value will always be either 52 or 53.
+///
+/// ```rust
+/// # use time::util::weeks_in_year;
+/// assert_eq!(weeks_in_year(2019), 52);
+/// assert_eq!(weeks_in_year(2020), 53);
+/// ```
+pub const fn weeks_in_year(year: i32) -> u8 {
+ match year.rem_euclid(400) {
+ 4 | 9 | 15 | 20 | 26 | 32 | 37 | 43 | 48 | 54 | 60 | 65 | 71 | 76 | 82 | 88 | 93 | 99
+ | 105 | 111 | 116 | 122 | 128 | 133 | 139 | 144 | 150 | 156 | 161 | 167 | 172 | 178
+ | 184 | 189 | 195 | 201 | 207 | 212 | 218 | 224 | 229 | 235 | 240 | 246 | 252 | 257
+ | 263 | 268 | 274 | 280 | 285 | 291 | 296 | 303 | 308 | 314 | 320 | 325 | 331 | 336
+ | 342 | 348 | 353 | 359 | 364 | 370 | 376 | 381 | 387 | 392 | 398 => 53,
+ _ => 52,
+ }
+}
diff --git a/third_party/rust/time-macros/.cargo-checksum.json b/third_party/rust/time-macros/.cargo-checksum.json
new file mode 100644
index 0000000000..98cc1d6441
--- /dev/null
+++ b/third_party/rust/time-macros/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"eb16c06efbfbf2ff5f48260785d4ecefbae6873d9d55c0ba2d388c6762e69b1f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/date.rs":"ffcd3d0998ec67abb43a3f8eccc6104172f5061b855312b89d53bb82fece2460","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"b597f98f425f1628b93ffea19f5f32163aa204e4cd25351bc114853a798e14b0","src/format_description/component.rs":"a05e7549db9bab4f3836f5fd5af18cacbfa6b323d0106b027e21bf438a5885e5","src/format_description/error.rs":"41253d7a02e14597915cf588811a272a90d1ce0f857f7769914e076dd5a66774","src/format_description/mod.rs":"da47af329408e9428753ad98ce433eaf026cfdd6e73e3142b23285251d32d0dd","src/format_description/modifier.rs":"c252c8a7d6608b594a6f715210ff67e804ae2f308025f62c8dd99d707627e4a9","src/format_description/parse.rs":"d65d6e7008030414ce6a860ff37c462c07ed89176a3f1462eeb46468a38fce7e","src/helpers/mod.rs":"54ce8e93512e18ef8761687eaac898a8227852a732f92aa5e80c28e23315bd0c","src/helpers/string.rs":"ba5699a4df344cbd71c4143f642f6bc07591f53978a9800d4b49ca1f461f87d9","src/lib.rs":"f99bded51bb861be5d708a3f756407f5b936a5febb719760c253a15113687e0d","src/offset.rs":"fc9341648e091b4d8f7bec47006c01c21cb038c7ef98bd36a492cf78e7533023","src/quote.rs":"b40251b0ca68e2362aff4297b87a027e48053f1a419113d3d0f7fe089a845a9c","src/serde_format_description.rs":"aa279c8005005fc87c52fa5e8be8ef8fc13ef456a18e3cd5d702ae81194ba4d9","src/time.rs":"3c06562358aed7ef624319c96e3f9c150a069606ab930de98ac379ef16b08100","src/to_tokens.rs":"825150a92396a019fee44f21da0bd257349e276d5e75a23ff86cfc625bef6f10"},"package":"d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"} \ No newline at end of file
diff --git a/third_party/rust/time-macros/Cargo.toml b/third_party/rust/time-macros/Cargo.toml
new file mode 100644
index 0000000000..c770e23ad4
--- /dev/null
+++ b/third_party/rust/time-macros/Cargo.toml
@@ -0,0 +1,45 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.60.0"
+name = "time-macros"
+version = "0.2.6"
+authors = [
+ "Jacob Pratt <open-source@jhpratt.dev>",
+ "Time contributors",
+]
+description = """
+ Procedural macros for the time crate.
+ This crate is an implementation detail and should not be relied upon directly.
+"""
+keywords = [
+ "date",
+ "time",
+ "calendar",
+ "duration",
+]
+categories = ["date-and-time"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/time-rs/time"
+
+[lib]
+proc-macro = true
+
+[dependencies.time-core]
+version = "=0.1.0"
+
+[features]
+formatting = []
+large-dates = []
+parsing = []
+serde = []
diff --git a/third_party/rust/time-macros/LICENSE-Apache b/third_party/rust/time-macros/LICENSE-Apache
new file mode 100644
index 0000000000..7646f21e37
--- /dev/null
+++ b/third_party/rust/time-macros/LICENSE-Apache
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2022 Jacob Pratt et al.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/rust/time-macros/LICENSE-MIT b/third_party/rust/time-macros/LICENSE-MIT
new file mode 100644
index 0000000000..a11a755732
--- /dev/null
+++ b/third_party/rust/time-macros/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright (c) 2022 Jacob Pratt et al.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/rust/time-macros/src/date.rs b/third_party/rust/time-macros/src/date.rs
new file mode 100644
index 0000000000..574ef8ce6f
--- /dev/null
+++ b/third_party/rust/time-macros/src/date.rs
@@ -0,0 +1,137 @@
+use std::iter::Peekable;
+
+use proc_macro::{token_stream, TokenTree};
+use time_core::util::{days_in_year, weeks_in_year};
+
+use crate::helpers::{
+ consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo,
+};
+use crate::to_tokens::ToTokenTree;
+use crate::Error;
+
+#[cfg(feature = "large-dates")]
+const MAX_YEAR: i32 = 999_999;
+#[cfg(not(feature = "large-dates"))]
+const MAX_YEAR: i32 = 9_999;
+
+pub(crate) struct Date {
+ pub(crate) year: i32,
+ pub(crate) ordinal: u16,
+}
+
+pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date, Error> {
+ let (year_sign_span, year_sign, explicit_sign) = if let Ok(span) = consume_punct('-', chars) {
+ (Some(span), -1, true)
+ } else if let Ok(span) = consume_punct('+', chars) {
+ (Some(span), 1, true)
+ } else {
+ (None, 1, false)
+ };
+ let (year_span, mut year) = consume_number::<i32>("year", chars)?;
+ year *= year_sign;
+ if year.abs() > MAX_YEAR {
+ return Err(Error::InvalidComponent {
+ name: "year",
+ value: year.to_string(),
+ span_start: Some(year_sign_span.unwrap_or(year_span)),
+ span_end: Some(year_span),
+ });
+ }
+ if !explicit_sign && year.abs() >= 10_000 {
+ return Err(Error::Custom {
+ message: "years with more than four digits must have an explicit sign".into(),
+ span_start: Some(year_sign_span.unwrap_or(year_span)),
+ span_end: Some(year_span),
+ });
+ }
+
+ consume_punct('-', chars)?;
+
+ // year-week-day
+ if let Ok(w_span) = consume_any_ident(&["W"], chars) {
+ let (week_span, week) = consume_number::<u8>("week", chars)?;
+ consume_punct('-', chars)?;
+ let (day_span, day) = consume_number::<u8>("day", chars)?;
+
+ if week > weeks_in_year(year) {
+ return Err(Error::InvalidComponent {
+ name: "week",
+ value: week.to_string(),
+ span_start: Some(w_span),
+ span_end: Some(week_span),
+ });
+ }
+ if day == 0 || day > 7 {
+ return Err(Error::InvalidComponent {
+ name: "day",
+ value: day.to_string(),
+ span_start: Some(day_span),
+ span_end: Some(day_span),
+ });
+ }
+
+ let (year, ordinal) = ywd_to_yo(year, week, day);
+
+ return Ok(Date { year, ordinal });
+ }
+
+ // We don't yet know whether it's year-month-day or year-ordinal.
+ let (month_or_ordinal_span, month_or_ordinal) =
+ consume_number::<u16>("month or ordinal", chars)?;
+
+ // year-month-day
+ #[allow(clippy::branches_sharing_code)] // clarity
+ if consume_punct('-', chars).is_ok() {
+ let (month_span, month) = (month_or_ordinal_span, month_or_ordinal);
+ let (day_span, day) = consume_number::<u8>("day", chars)?;
+
+ if month == 0 || month > 12 {
+ return Err(Error::InvalidComponent {
+ name: "month",
+ value: month.to_string(),
+ span_start: Some(month_span),
+ span_end: Some(month_span),
+ });
+ }
+ let month = month as _;
+ if day == 0 || day > days_in_year_month(year, month) {
+ return Err(Error::InvalidComponent {
+ name: "day",
+ value: day.to_string(),
+ span_start: Some(day_span),
+ span_end: Some(day_span),
+ });
+ }
+
+ let (year, ordinal) = ymd_to_yo(year, month, day);
+
+ Ok(Date { year, ordinal })
+ }
+ // year-ordinal
+ else {
+ let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal);
+
+ if ordinal == 0 || ordinal > days_in_year(year) {
+ return Err(Error::InvalidComponent {
+ name: "ordinal",
+ value: ordinal.to_string(),
+ span_start: Some(ordinal_span),
+ span_end: Some(ordinal_span),
+ });
+ }
+
+ Ok(Date { year, ordinal })
+ }
+}
+
+impl ToTokenTree for Date {
+ fn into_token_tree(self) -> TokenTree {
+ quote_group! {{
+ const DATE: ::time::Date = ::time::Date::__from_ordinal_date_unchecked(
+ #(self.year),
+ #(self.ordinal),
+ );
+ DATE
+ }}
+ }
+}
diff --git a/third_party/rust/time-macros/src/datetime.rs b/third_party/rust/time-macros/src/datetime.rs
new file mode 100644
index 0000000000..2d41e9a532
--- /dev/null
+++ b/third_party/rust/time-macros/src/datetime.rs
@@ -0,0 +1,57 @@
+use std::iter::Peekable;
+
+use proc_macro::{token_stream, Ident, Span, TokenTree};
+
+use crate::date::Date;
+use crate::error::Error;
+use crate::offset::Offset;
+use crate::time::Time;
+use crate::to_tokens::ToTokenTree;
+use crate::{date, offset, time};
+
+pub(crate) struct DateTime {
+ date: Date,
+ time: Time,
+ offset: Option<Offset>,
+}
+
+pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<DateTime, Error> {
+ let date = date::parse(chars)?;
+ let time = time::parse(chars)?;
+ let offset = match offset::parse(chars) {
+ Ok(offset) => Some(offset),
+ Err(Error::UnexpectedEndOfInput | Error::MissingComponent { name: "sign", .. }) => None,
+ Err(err) => return Err(err),
+ };
+
+ if let Some(token) = chars.peek() {
+ return Err(Error::UnexpectedToken {
+ tree: token.clone(),
+ });
+ }
+
+ Ok(DateTime { date, time, offset })
+}
+
+impl ToTokenTree for DateTime {
+ fn into_token_tree(self) -> TokenTree {
+ let (type_name, maybe_offset) = match self.offset {
+ Some(offset) => (
+ Ident::new("OffsetDateTime", Span::mixed_site()),
+ quote!(.assume_offset(#(offset))),
+ ),
+ None => (
+ Ident::new("PrimitiveDateTime", Span::mixed_site()),
+ quote!(),
+ ),
+ };
+
+ quote_group! {{
+ const DATE_TIME: ::time::#(type_name) = ::time::PrimitiveDateTime::new(
+ #(self.date),
+ #(self.time),
+ ) #S(maybe_offset);
+ DATE_TIME
+ }}
+ }
+}
diff --git a/third_party/rust/time-macros/src/error.rs b/third_party/rust/time-macros/src/error.rs
new file mode 100644
index 0000000000..4de369dafc
--- /dev/null
+++ b/third_party/rust/time-macros/src/error.rs
@@ -0,0 +1,136 @@
+use std::borrow::Cow;
+use std::fmt;
+
+use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+use crate::format_description::error::InvalidFormatDescription;
+
+trait WithSpan {
+ fn with_span(self, span: Span) -> Self;
+}
+
+impl WithSpan for TokenTree {
+ fn with_span(mut self, span: Span) -> Self {
+ self.set_span(span);
+ self
+ }
+}
+
+pub(crate) enum Error {
+ MissingComponent {
+ name: &'static str,
+ span_start: Option<Span>,
+ span_end: Option<Span>,
+ },
+ InvalidComponent {
+ name: &'static str,
+ value: String,
+ span_start: Option<Span>,
+ span_end: Option<Span>,
+ },
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ ExpectedString {
+ span_start: Option<Span>,
+ span_end: Option<Span>,
+ },
+ UnexpectedToken {
+ tree: TokenTree,
+ },
+ UnexpectedEndOfInput,
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ InvalidFormatDescription {
+ error: InvalidFormatDescription,
+ span_start: Option<Span>,
+ span_end: Option<Span>,
+ },
+ Custom {
+ message: Cow<'static, str>,
+ span_start: Option<Span>,
+ span_end: Option<Span>,
+ },
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::MissingComponent { name, .. } => write!(f, "missing component: {name}"),
+ Self::InvalidComponent { name, value, .. } => {
+ write!(f, "invalid component: {name} was {value}")
+ }
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ Self::ExpectedString { .. } => f.write_str("expected string"),
+ Self::UnexpectedToken { tree } => write!(f, "unexpected token: {tree}"),
+ Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"),
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ Self::InvalidFormatDescription { error, .. } => error.fmt(f),
+ Self::Custom { message, .. } => f.write_str(message),
+ }
+ }
+}
+
+impl Error {
+ fn span_start(&self) -> Span {
+ match self {
+ Self::MissingComponent { span_start, .. }
+ | Self::InvalidComponent { span_start, .. }
+ | Self::Custom { span_start, .. } => *span_start,
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ Self::ExpectedString { span_start, .. }
+ | Self::InvalidFormatDescription { span_start, .. } => *span_start,
+ Self::UnexpectedToken { tree } => Some(tree.span()),
+ Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
+ }
+ .unwrap_or_else(Span::mixed_site)
+ }
+
+ fn span_end(&self) -> Span {
+ match self {
+ Self::MissingComponent { span_end, .. }
+ | Self::InvalidComponent { span_end, .. }
+ | Self::Custom { span_end, .. } => *span_end,
+ #[cfg(any(feature = "formatting", feature = "parsing"))]
+ Self::ExpectedString { span_end, .. }
+ | Self::InvalidFormatDescription { span_end, .. } => *span_end,
+ Self::UnexpectedToken { tree, .. } => Some(tree.span()),
+ Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
+ }
+ .unwrap_or_else(|| self.span_start())
+ }
+
+ pub(crate) fn to_compile_error(&self) -> TokenStream {
+ let (start, end) = (self.span_start(), self.span_end());
+
+ [
+ TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
+ TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
+ TokenTree::from(Ident::new("core", start)),
+ TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
+ TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
+ TokenTree::from(Ident::new("compile_error", start)),
+ TokenTree::from(Punct::new('!', Spacing::Alone)).with_span(start),
+ TokenTree::from(Group::new(
+ Delimiter::Parenthesis,
+ TokenStream::from(
+ TokenTree::from(Literal::string(&self.to_string())).with_span(end),
+ ),
+ ))
+ .with_span(end),
+ ]
+ .iter()
+ .cloned()
+ .collect()
+ }
+
+ /// Like `to_compile_error`, but for use in macros that produce items.
+ #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
+ pub(crate) fn to_compile_error_standalone(&self) -> TokenStream {
+ let end = self.span_end();
+ self.to_compile_error()
+ .into_iter()
+ .chain(std::iter::once(
+ TokenTree::from(Punct::new(';', Spacing::Alone)).with_span(end),
+ ))
+ .collect()
+ }
+}
diff --git a/third_party/rust/time-macros/src/format_description/component.rs b/third_party/rust/time-macros/src/format_description/component.rs
new file mode 100644
index 0000000000..850da91d20
--- /dev/null
+++ b/third_party/rust/time-macros/src/format_description/component.rs
@@ -0,0 +1,168 @@
+use proc_macro::{Ident, Span, TokenStream};
+
+use crate::format_description::error::InvalidFormatDescription;
+use crate::format_description::modifier;
+use crate::format_description::modifier::Modifiers;
+use crate::to_tokens::ToTokenStream;
+
+pub(crate) enum Component {
+ Day(modifier::Day),
+ Month(modifier::Month),
+ Ordinal(modifier::Ordinal),
+ Weekday(modifier::Weekday),
+ WeekNumber(modifier::WeekNumber),
+ Year(modifier::Year),
+ Hour(modifier::Hour),
+ Minute(modifier::Minute),
+ Period(modifier::Period),
+ Second(modifier::Second),
+ Subsecond(modifier::Subsecond),
+ OffsetHour(modifier::OffsetHour),
+ OffsetMinute(modifier::OffsetMinute),
+ OffsetSecond(modifier::OffsetSecond),
+}
+
+impl ToTokenStream for Component {
+ fn append_to(self, ts: &mut TokenStream) {
+ let mut mts = TokenStream::new();
+
+ macro_rules! component_name_and_append {
+ ($($name:ident)*) => {
+ match self {
+ $(Self::$name(modifier) => {
+ modifier.append_to(&mut mts);
+ stringify!($name)
+ })*
+ }
+ };
+ }
+
+ let component = component_name_and_append![
+ Day
+ Month
+ Ordinal
+ Weekday
+ WeekNumber
+ Year
+ Hour
+ Minute
+ Period
+ Second
+ Subsecond
+ OffsetHour
+ OffsetMinute
+ OffsetSecond
+ ];
+ let component = Ident::new(component, Span::mixed_site());
+
+ quote_append! { ts
+ ::time::format_description::Component::#(component)(#S(mts))
+ }
+ }
+}
+
+pub(crate) enum NakedComponent {
+ Day,
+ Month,
+ Ordinal,
+ Weekday,
+ WeekNumber,
+ Year,
+ Hour,
+ Minute,
+ Period,
+ Second,
+ Subsecond,
+ OffsetHour,
+ OffsetMinute,
+ OffsetSecond,
+}
+
+impl NakedComponent {
+ pub(crate) fn parse(
+ component_name: &[u8],
+ component_index: usize,
+ ) -> Result<Self, InvalidFormatDescription> {
+ match component_name {
+ b"day" => Ok(Self::Day),
+ b"month" => Ok(Self::Month),
+ b"ordinal" => Ok(Self::Ordinal),
+ b"weekday" => Ok(Self::Weekday),
+ b"week_number" => Ok(Self::WeekNumber),
+ b"year" => Ok(Self::Year),
+ b"hour" => Ok(Self::Hour),
+ b"minute" => Ok(Self::Minute),
+ b"period" => Ok(Self::Period),
+ b"second" => Ok(Self::Second),
+ b"subsecond" => Ok(Self::Subsecond),
+ b"offset_hour" => Ok(Self::OffsetHour),
+ b"offset_minute" => Ok(Self::OffsetMinute),
+ b"offset_second" => Ok(Self::OffsetSecond),
+ b"" => Err(InvalidFormatDescription::MissingComponentName {
+ index: component_index,
+ }),
+ _ => Err(InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(component_name).into_owned(),
+ index: component_index,
+ }),
+ }
+ }
+
+ pub(crate) fn attach_modifiers(self, modifiers: Modifiers) -> Component {
+ match self {
+ Self::Day => Component::Day(modifier::Day {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::Month => Component::Month(modifier::Month {
+ padding: modifiers.padding.unwrap_or_default(),
+ repr: modifiers.month_repr.unwrap_or_default(),
+ case_sensitive: modifiers.case_sensitive.unwrap_or(true),
+ }),
+ Self::Ordinal => Component::Ordinal(modifier::Ordinal {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::Weekday => Component::Weekday(modifier::Weekday {
+ repr: modifiers.weekday_repr.unwrap_or_default(),
+ one_indexed: modifiers.weekday_is_one_indexed.unwrap_or(true),
+ case_sensitive: modifiers.case_sensitive.unwrap_or(true),
+ }),
+ Self::WeekNumber => Component::WeekNumber(modifier::WeekNumber {
+ padding: modifiers.padding.unwrap_or_default(),
+ repr: modifiers.week_number_repr.unwrap_or_default(),
+ }),
+ Self::Year => Component::Year(modifier::Year {
+ padding: modifiers.padding.unwrap_or_default(),
+ repr: modifiers.year_repr.unwrap_or_default(),
+ iso_week_based: modifiers.year_is_iso_week_based.unwrap_or_default(),
+ sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(),
+ }),
+ Self::Hour => Component::Hour(modifier::Hour {
+ padding: modifiers.padding.unwrap_or_default(),
+ is_12_hour_clock: modifiers.hour_is_12_hour_clock.unwrap_or_default(),
+ }),
+ Self::Minute => Component::Minute(modifier::Minute {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::Period => Component::Period(modifier::Period {
+ is_uppercase: modifiers.period_is_uppercase.unwrap_or(true),
+ case_sensitive: modifiers.case_sensitive.unwrap_or(true),
+ }),
+ Self::Second => Component::Second(modifier::Second {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::Subsecond => Component::Subsecond(modifier::Subsecond {
+ digits: modifiers.subsecond_digits.unwrap_or_default(),
+ }),
+ Self::OffsetHour => Component::OffsetHour(modifier::OffsetHour {
+ sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(),
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::OffsetMinute => Component::OffsetMinute(modifier::OffsetMinute {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ Self::OffsetSecond => Component::OffsetSecond(modifier::OffsetSecond {
+ padding: modifiers.padding.unwrap_or_default(),
+ }),
+ }
+ }
+}
diff --git a/third_party/rust/time-macros/src/format_description/error.rs b/third_party/rust/time-macros/src/format_description/error.rs
new file mode 100644
index 0000000000..9aacd7dc9e
--- /dev/null
+++ b/third_party/rust/time-macros/src/format_description/error.rs
@@ -0,0 +1,29 @@
+use std::fmt;
+
+pub(crate) enum InvalidFormatDescription {
+ UnclosedOpeningBracket { index: usize },
+ InvalidComponentName { name: String, index: usize },
+ InvalidModifier { value: String, index: usize },
+ MissingComponentName { index: usize },
+}
+
+impl fmt::Display for InvalidFormatDescription {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ #[allow(clippy::enum_glob_use)]
+ 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}")
+ }
+ }
+ }
+}
diff --git a/third_party/rust/time-macros/src/format_description/mod.rs b/third_party/rust/time-macros/src/format_description/mod.rs
new file mode 100644
index 0000000000..dd32db74d5
--- /dev/null
+++ b/third_party/rust/time-macros/src/format_description/mod.rs
@@ -0,0 +1,40 @@
+mod component;
+pub(crate) mod error;
+pub(crate) mod modifier;
+pub(crate) mod parse;
+
+use proc_macro::{Literal, TokenStream};
+
+pub(crate) use self::component::Component;
+pub(crate) use self::parse::parse;
+use crate::to_tokens::ToTokenStream;
+
+mod helper {
+ #[must_use = "This does not modify the original slice."]
+ pub(crate) fn consume_whitespace<'a>(bytes: &'a [u8], index: &mut usize) -> &'a [u8] {
+ let first_non_whitespace = bytes
+ .iter()
+ .position(|c| !c.is_ascii_whitespace())
+ .unwrap_or(bytes.len());
+ *index += first_non_whitespace;
+ &bytes[first_non_whitespace..]
+ }
+}
+
+#[allow(single_use_lifetimes)] // false positive
+#[allow(variant_size_differences)]
+pub(crate) enum FormatItem<'a> {
+ Literal(&'a [u8]),
+ Component(Component),
+}
+
+impl ToTokenStream for FormatItem<'_> {
+ fn append_to(self, ts: &mut TokenStream) {
+ quote_append! { ts
+ ::time::format_description::FormatItem::#S(match self {
+ FormatItem::Literal(bytes) => quote! { Literal(#(Literal::byte_string(bytes))) },
+ FormatItem::Component(component) => quote! { Component(#S(component)) },
+ })
+ }
+ }
+}
diff --git a/third_party/rust/time-macros/src/format_description/modifier.rs b/third_party/rust/time-macros/src/format_description/modifier.rs
new file mode 100644
index 0000000000..f4e641a7b9
--- /dev/null
+++ b/third_party/rust/time-macros/src/format_description/modifier.rs
@@ -0,0 +1,417 @@
+use core::mem;
+
+use proc_macro::{Ident, Span, TokenStream, TokenTree};
+
+use crate::format_description::error::InvalidFormatDescription;
+use crate::format_description::helper;
+use crate::to_tokens::{ToTokenStream, ToTokenTree};
+
+macro_rules! to_tokens {
+ (
+ $(#[$struct_attr:meta])*
+ $struct_vis:vis struct $struct_name:ident {$(
+ $(#[$field_attr:meta])*
+ $field_vis:vis $field_name:ident : $field_ty:ty
+ ),+ $(,)?}
+ ) => {
+ $(#[$struct_attr])*
+ $struct_vis struct $struct_name {$(
+ $(#[$field_attr])*
+ $field_vis $field_name: $field_ty
+ ),+}
+
+ impl ToTokenTree for $struct_name {
+ fn into_token_tree(self) -> TokenTree {
+ let mut tokens = TokenStream::new();
+ let Self {$($field_name),+} = self;
+
+ quote_append! { tokens
+ let mut value = ::time::format_description::modifier::$struct_name::default();
+ };
+ $(
+ quote_append!(tokens value.$field_name =);
+ $field_name.append_to(&mut tokens);
+ quote_append!(tokens ;);
+ )+
+ quote_append!(tokens value);
+
+ proc_macro::TokenTree::Group(proc_macro::Group::new(
+ proc_macro::Delimiter::Brace,
+ tokens,
+ ))
+ }
+ }
+ };
+
+ (
+ $(#[$enum_attr:meta])*
+ $enum_vis:vis enum $enum_name:ident {$(
+ $(#[$variant_attr:meta])*
+ $variant_name:ident
+ ),+ $(,)?}
+ ) => {
+ $(#[$enum_attr])*
+ $enum_vis enum $enum_name {$(
+ $(#[$variant_attr])*
+ $variant_name
+ ),+}
+
+ impl ToTokenStream for $enum_name {
+ fn append_to(self, ts: &mut TokenStream) {
+ quote_append! { ts
+ ::time::format_description::modifier::$enum_name::
+ };
+ let name = match self {
+ $(Self::$variant_name => stringify!($variant_name)),+
+ };
+ ts.extend([TokenTree::Ident(Ident::new(name, Span::mixed_site()))]);
+ }
+ }
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Day {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum MonthRepr {
+ Numerical,
+ Long,
+ Short,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Month {
+ pub(crate) padding: Padding,
+ pub(crate) repr: MonthRepr,
+ pub(crate) case_sensitive: bool,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Ordinal {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum WeekdayRepr {
+ Short,
+ Long,
+ Sunday,
+ Monday,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Weekday {
+ pub(crate) repr: WeekdayRepr,
+ pub(crate) one_indexed: bool,
+ pub(crate) case_sensitive: bool,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum WeekNumberRepr {
+ Iso,
+ Sunday,
+ Monday,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct WeekNumber {
+ pub(crate) padding: Padding,
+ pub(crate) repr: WeekNumberRepr,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum YearRepr {
+ Full,
+ LastTwo,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Year {
+ pub(crate) padding: Padding,
+ pub(crate) repr: YearRepr,
+ pub(crate) iso_week_based: bool,
+ pub(crate) sign_is_mandatory: bool,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Hour {
+ pub(crate) padding: Padding,
+ pub(crate) is_12_hour_clock: bool,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Minute {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Period {
+ pub(crate) is_uppercase: bool,
+ pub(crate) case_sensitive: bool,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Second {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum SubsecondDigits {
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+ Nine,
+ OneOrMore,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct Subsecond {
+ pub(crate) digits: SubsecondDigits,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct OffsetHour {
+ pub(crate) sign_is_mandatory: bool,
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct OffsetMinute {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) struct OffsetSecond {
+ pub(crate) padding: Padding,
+ }
+}
+
+to_tokens! {
+ pub(crate) enum Padding {
+ Space,
+ Zero,
+ None,
+ }
+}
+
+macro_rules! impl_default {
+ ($($type:ty => $default:expr;)*) => {$(
+ impl Default for $type {
+ fn default() -> Self {
+ $default
+ }
+ }
+ )*};
+}
+
+impl_default! {
+ Day => Self { padding: Padding::default() };
+ MonthRepr => Self::Numerical;
+ Month => Self {
+ padding: Padding::default(),
+ repr: MonthRepr::default(),
+ case_sensitive: true,
+ };
+ Ordinal => Self { padding: Padding::default() };
+ WeekdayRepr => Self::Long;
+ Weekday => Self {
+ repr: WeekdayRepr::default(),
+ one_indexed: true,
+ case_sensitive: true,
+ };
+ WeekNumberRepr => Self::Iso;
+ WeekNumber => Self {
+ padding: Padding::default(),
+ repr: WeekNumberRepr::default(),
+ };
+ YearRepr => Self::Full;
+ Year => Self {
+ padding: Padding::default(),
+ repr: YearRepr::default(),
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ };
+ Hour => Self {
+ padding: Padding::default(),
+ is_12_hour_clock: false,
+ };
+ Minute => Self { padding: Padding::default() };
+ Period => Self { is_uppercase: true, case_sensitive: true };
+ Second => Self { padding: Padding::default() };
+ SubsecondDigits => Self::OneOrMore;
+ Subsecond => Self { digits: SubsecondDigits::default() };
+ OffsetHour => Self {
+ sign_is_mandatory: true,
+ padding: Padding::default(),
+ };
+ OffsetMinute => Self { padding: Padding::default() };
+ OffsetSecond => Self { padding: Padding::default() };
+ Padding => Self::Zero;
+}
+
+#[derive(Default)]
+pub(crate) struct Modifiers {
+ pub(crate) padding: Option<Padding>,
+ pub(crate) hour_is_12_hour_clock: Option<bool>,
+ pub(crate) period_is_uppercase: Option<bool>,
+ pub(crate) month_repr: Option<MonthRepr>,
+ pub(crate) subsecond_digits: Option<SubsecondDigits>,
+ pub(crate) weekday_repr: Option<WeekdayRepr>,
+ pub(crate) weekday_is_one_indexed: Option<bool>,
+ pub(crate) week_number_repr: Option<WeekNumberRepr>,
+ pub(crate) year_repr: Option<YearRepr>,
+ pub(crate) year_is_iso_week_based: Option<bool>,
+ pub(crate) sign_is_mandatory: Option<bool>,
+ pub(crate) case_sensitive: Option<bool>,
+}
+
+impl Modifiers {
+ #[allow(clippy::too_many_lines)]
+ pub(crate) fn parse(
+ component_name: &[u8],
+ mut bytes: &[u8],
+ index: &mut usize,
+ ) -> Result<Self, InvalidFormatDescription> {
+ let mut modifiers = Self::default();
+
+ while !bytes.is_empty() {
+ // Trim any whitespace between modifiers.
+ bytes = helper::consume_whitespace(bytes, index);
+
+ let modifier;
+ if let Some(whitespace_loc) = bytes.iter().position(u8::is_ascii_whitespace) {
+ *index += whitespace_loc;
+ modifier = &bytes[..whitespace_loc];
+ bytes = &bytes[whitespace_loc..];
+ } else {
+ modifier = mem::take(&mut bytes);
+ }
+
+ if modifier.is_empty() {
+ break;
+ }
+
+ match (component_name, modifier) {
+ (
+ b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
+ | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
+ b"padding:space",
+ ) => modifiers.padding = Some(Padding::Space),
+ (
+ b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
+ | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
+ b"padding:zero",
+ ) => modifiers.padding = Some(Padding::Zero),
+ (
+ b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
+ | b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
+ b"padding:none",
+ ) => modifiers.padding = Some(Padding::None),
+ (b"hour", b"repr:24") => modifiers.hour_is_12_hour_clock = Some(false),
+ (b"hour", b"repr:12") => modifiers.hour_is_12_hour_clock = Some(true),
+ (b"month" | b"period" | b"weekday", b"case_sensitive:true") => {
+ modifiers.case_sensitive = Some(true)
+ }
+ (b"month" | b"period" | b"weekday", b"case_sensitive:false") => {
+ modifiers.case_sensitive = Some(false)
+ }
+ (b"month", b"repr:numerical") => modifiers.month_repr = Some(MonthRepr::Numerical),
+ (b"month", b"repr:long") => modifiers.month_repr = Some(MonthRepr::Long),
+ (b"month", b"repr:short") => modifiers.month_repr = Some(MonthRepr::Short),
+ (b"offset_hour" | b"year", b"sign:automatic") => {
+ modifiers.sign_is_mandatory = Some(false);
+ }
+ (b"offset_hour" | b"year", b"sign:mandatory") => {
+ modifiers.sign_is_mandatory = Some(true);
+ }
+ (b"period", b"case:upper") => modifiers.period_is_uppercase = Some(true),
+ (b"period", b"case:lower") => modifiers.period_is_uppercase = Some(false),
+ (b"subsecond", b"digits:1") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::One);
+ }
+ (b"subsecond", b"digits:2") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Two);
+ }
+ (b"subsecond", b"digits:3") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Three);
+ }
+ (b"subsecond", b"digits:4") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Four);
+ }
+ (b"subsecond", b"digits:5") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Five);
+ }
+ (b"subsecond", b"digits:6") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Six);
+ }
+ (b"subsecond", b"digits:7") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Seven);
+ }
+ (b"subsecond", b"digits:8") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Eight);
+ }
+ (b"subsecond", b"digits:9") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::Nine);
+ }
+ (b"subsecond", b"digits:1+") => {
+ modifiers.subsecond_digits = Some(SubsecondDigits::OneOrMore);
+ }
+ (b"weekday", b"repr:short") => modifiers.weekday_repr = Some(WeekdayRepr::Short),
+ (b"weekday", b"repr:long") => modifiers.weekday_repr = Some(WeekdayRepr::Long),
+ (b"weekday", b"repr:sunday") => modifiers.weekday_repr = Some(WeekdayRepr::Sunday),
+ (b"weekday", b"repr:monday") => modifiers.weekday_repr = Some(WeekdayRepr::Monday),
+ (b"weekday", b"one_indexed:true") => modifiers.weekday_is_one_indexed = Some(true),
+ (b"weekday", b"one_indexed:false") => {
+ modifiers.weekday_is_one_indexed = Some(false);
+ }
+ (b"week_number", b"repr:iso") => {
+ modifiers.week_number_repr = Some(WeekNumberRepr::Iso);
+ }
+ (b"week_number", b"repr:sunday") => {
+ modifiers.week_number_repr = Some(WeekNumberRepr::Sunday);
+ }
+ (b"week_number", b"repr:monday") => {
+ modifiers.week_number_repr = Some(WeekNumberRepr::Monday);
+ }
+ (b"year", b"repr:full") => modifiers.year_repr = Some(YearRepr::Full),
+ (b"year", b"repr:last_two") => modifiers.year_repr = Some(YearRepr::LastTwo),
+ (b"year", b"base:calendar") => modifiers.year_is_iso_week_based = Some(false),
+ (b"year", b"base:iso_week") => modifiers.year_is_iso_week_based = Some(true),
+ _ => {
+ return Err(InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(modifier).into_owned(),
+ index: *index,
+ });
+ }
+ }
+ }
+
+ Ok(modifiers)
+ }
+}
diff --git a/third_party/rust/time-macros/src/format_description/parse.rs b/third_party/rust/time-macros/src/format_description/parse.rs
new file mode 100644
index 0000000000..19c7bf6080
--- /dev/null
+++ b/third_party/rust/time-macros/src/format_description/parse.rs
@@ -0,0 +1,84 @@
+use proc_macro::Span;
+
+use crate::format_description::component::{Component, NakedComponent};
+use crate::format_description::error::InvalidFormatDescription;
+use crate::format_description::{helper, modifier, FormatItem};
+use crate::Error;
+
+struct ParsedItem<'a> {
+ item: FormatItem<'a>,
+ remaining: &'a [u8],
+}
+
+fn parse_component(mut s: &[u8], index: &mut usize) -> Result<Component, InvalidFormatDescription> {
+ s = helper::consume_whitespace(s, index);
+
+ let component_index = *index;
+ let whitespace_loc = s
+ .iter()
+ .position(u8::is_ascii_whitespace)
+ .unwrap_or(s.len());
+ *index += whitespace_loc;
+ let component_name = &s[..whitespace_loc];
+ s = &s[whitespace_loc..];
+ s = helper::consume_whitespace(s, index);
+
+ Ok(NakedComponent::parse(component_name, component_index)?
+ .attach_modifiers(modifier::Modifiers::parse(component_name, s, index)?))
+}
+
+fn parse_literal<'a>(s: &'a [u8], index: &mut usize) -> ParsedItem<'a> {
+ let loc = s.iter().position(|&c| c == b'[').unwrap_or(s.len());
+ *index += loc;
+ ParsedItem {
+ item: FormatItem::Literal(&s[..loc]),
+ remaining: &s[loc..],
+ }
+}
+
+fn parse_item<'a>(
+ s: &'a [u8],
+ index: &mut usize,
+) -> Result<ParsedItem<'a>, InvalidFormatDescription> {
+ if let [b'[', b'[', remaining @ ..] = s {
+ *index += 2;
+ return Ok(ParsedItem {
+ item: FormatItem::Literal(b"["),
+ remaining,
+ });
+ };
+
+ if s.starts_with(b"[") {
+ if let Some(bracket_index) = s.iter().position(|&c| c == b']') {
+ *index += 1; // opening bracket
+ let ret_val = ParsedItem {
+ item: FormatItem::Component(parse_component(&s[1..bracket_index], index)?),
+ remaining: &s[bracket_index + 1..],
+ };
+ *index += 1; // closing bracket
+ Ok(ret_val)
+ } else {
+ Err(InvalidFormatDescription::UnclosedOpeningBracket { index: *index })
+ }
+ } else {
+ Ok(parse_literal(s, index))
+ }
+}
+
+pub(crate) fn parse(mut s: &[u8], span: Span) -> Result<Vec<FormatItem<'_>>, Error> {
+ let mut compound = Vec::new();
+ let mut loc = 0;
+
+ while !s.is_empty() {
+ let ParsedItem { item, remaining } =
+ parse_item(s, &mut loc).map_err(|error| Error::InvalidFormatDescription {
+ error,
+ span_start: Some(span),
+ span_end: Some(span),
+ })?;
+ s = remaining;
+ compound.push(item);
+ }
+
+ Ok(compound)
+}
diff --git a/third_party/rust/time-macros/src/helpers/mod.rs b/third_party/rust/time-macros/src/helpers/mod.rs
new file mode 100644
index 0000000000..cbf3ba3ed5
--- /dev/null
+++ b/third_party/rust/time-macros/src/helpers/mod.rs
@@ -0,0 +1,129 @@
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+mod string;
+
+use std::iter::Peekable;
+use std::str::FromStr;
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+use proc_macro::TokenStream;
+use proc_macro::{token_stream, Span, TokenTree};
+use time_core::util::{days_in_year, is_leap_year};
+
+use crate::Error;
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub(crate) fn get_string_literal(tokens: TokenStream) -> Result<(Span, Vec<u8>), Error> {
+ let mut tokens = tokens.into_iter();
+
+ match (tokens.next(), tokens.next()) {
+ (Some(TokenTree::Literal(literal)), None) => string::parse(&literal),
+ (Some(tree), None) => Err(Error::ExpectedString {
+ span_start: Some(tree.span()),
+ span_end: Some(tree.span()),
+ }),
+ (_, Some(tree)) => Err(Error::UnexpectedToken { tree }),
+ (None, None) => Err(Error::ExpectedString {
+ span_start: None,
+ span_end: None,
+ }),
+ }
+}
+
+pub(crate) fn consume_number<T: FromStr>(
+ component_name: &'static str,
+ chars: &mut Peekable<token_stream::IntoIter>,
+) -> Result<(Span, T), Error> {
+ let (span, digits) = match chars.next() {
+ Some(TokenTree::Literal(literal)) => (literal.span(), literal.to_string()),
+ Some(tree) => return Err(Error::UnexpectedToken { tree }),
+ None => return Err(Error::UnexpectedEndOfInput),
+ };
+
+ if let Ok(value) = digits.replace('_', "").parse() {
+ Ok((span, value))
+ } else {
+ Err(Error::InvalidComponent {
+ name: component_name,
+ value: digits,
+ span_start: Some(span),
+ span_end: Some(span),
+ })
+ }
+}
+
+pub(crate) fn consume_any_ident(
+ idents: &[&str],
+ chars: &mut Peekable<token_stream::IntoIter>,
+) -> Result<Span, Error> {
+ match chars.peek() {
+ Some(TokenTree::Ident(char)) if idents.contains(&char.to_string().as_str()) => {
+ let ret = Ok(char.span());
+ drop(chars.next());
+ ret
+ }
+ Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }),
+ None => Err(Error::UnexpectedEndOfInput),
+ }
+}
+
+pub(crate) fn consume_punct(
+ c: char,
+ chars: &mut Peekable<token_stream::IntoIter>,
+) -> Result<Span, Error> {
+ match chars.peek() {
+ Some(TokenTree::Punct(punct)) if *punct == c => {
+ let ret = Ok(punct.span());
+ drop(chars.next());
+ ret
+ }
+ Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }),
+ None => Err(Error::UnexpectedEndOfInput),
+ }
+}
+
+fn jan_weekday(year: i32, ordinal: i32) -> u8 {
+ macro_rules! div_floor {
+ ($a:expr, $b:expr) => {{
+ let (_quotient, _remainder) = ($a / $b, $a % $b);
+ if (_remainder > 0 && $b < 0) || (_remainder < 0 && $b > 0) {
+ _quotient - 1
+ } else {
+ _quotient
+ }
+ }};
+ }
+
+ let adj_year = year - 1;
+ ((ordinal + adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
+ + div_floor!(adj_year, 400)
+ + 6)
+ .rem_euclid(7)) as _
+}
+
+pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 {
+ [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month as usize - 1]
+ + (month == 2 && is_leap_year(year)) as u8
+}
+
+pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u16) {
+ let (ordinal, overflow) = (u16::from(week) * 7 + u16::from(iso_weekday_number))
+ .overflowing_sub(u16::from(jan_weekday(year, 4)) + 4);
+
+ if overflow || ordinal == 0 {
+ return (year - 1, (ordinal.wrapping_add(days_in_year(year - 1))));
+ }
+
+ let days_in_cur_year = days_in_year(year);
+ if ordinal > days_in_cur_year {
+ (year + 1, ordinal - days_in_cur_year)
+ } else {
+ (year, ordinal)
+ }
+}
+
+pub(crate) fn ymd_to_yo(year: i32, month: u8, day: u8) -> (i32, u16) {
+ let ordinal = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334][month as usize - 1]
+ + (month > 2 && is_leap_year(year)) as u16;
+
+ (year, ordinal + u16::from(day))
+}
diff --git a/third_party/rust/time-macros/src/helpers/string.rs b/third_party/rust/time-macros/src/helpers/string.rs
new file mode 100644
index 0000000000..fa3780f5e3
--- /dev/null
+++ b/third_party/rust/time-macros/src/helpers/string.rs
@@ -0,0 +1,188 @@
+use std::ops::{Index, RangeFrom};
+
+use proc_macro::Span;
+
+use crate::Error;
+
+pub(crate) fn parse(token: &proc_macro::Literal) -> Result<(Span, Vec<u8>), Error> {
+ let span = token.span();
+ let repr = token.to_string();
+
+ match repr.as_bytes() {
+ [b'"', ..] => Ok((span, parse_lit_str_cooked(&repr[1..]))),
+ [b'b', b'"', rest @ ..] => Ok((span, parse_lit_byte_str_cooked(rest))),
+ [b'r', rest @ ..] | [b'b', b'r', rest @ ..] => Ok((span, parse_lit_str_raw(rest))),
+ _ => Err(Error::ExpectedString {
+ span_start: Some(span),
+ span_end: Some(span),
+ }),
+ }
+}
+
+fn byte(s: impl AsRef<[u8]>, idx: usize) -> u8 {
+ s.as_ref().get(idx).copied().unwrap_or_default()
+}
+
+fn parse_lit_str_cooked(mut s: &str) -> Vec<u8> {
+ let mut content = String::new();
+ 'outer: loop {
+ let ch = match byte(s, 0) {
+ b'"' => break,
+ b'\\' => {
+ let b = byte(s, 1);
+ s = &s[2..];
+ match b {
+ b'x' => {
+ let (byte, rest) = backslash_x(s);
+ s = rest;
+ char::from_u32(u32::from(byte)).expect("byte was just validated")
+ }
+ b'u' => {
+ let (chr, rest) = backslash_u(s);
+ s = rest;
+ chr
+ }
+ b'n' => '\n',
+ b'r' => '\r',
+ b't' => '\t',
+ b'\\' => '\\',
+ b'0' => '\0',
+ b'\'' => '\'',
+ b'"' => '"',
+ b'\r' | b'\n' => loop {
+ let ch = s.chars().next().unwrap_or_default();
+ if ch.is_whitespace() {
+ s = &s[ch.len_utf8()..];
+ } else {
+ continue 'outer;
+ }
+ },
+ _ => unreachable!("invalid escape"),
+ }
+ }
+ b'\r' => {
+ // bare CR not permitted
+ s = &s[2..];
+ '\n'
+ }
+ _ => {
+ let ch = s.chars().next().unwrap_or_default();
+ s = &s[ch.len_utf8()..];
+ ch
+ }
+ };
+ content.push(ch);
+ }
+
+ content.into_bytes()
+}
+
+fn parse_lit_str_raw(s: &[u8]) -> Vec<u8> {
+ let mut pounds = 0;
+ while byte(s, pounds) == b'#' {
+ pounds += 1;
+ }
+ let close = s
+ .iter()
+ .rposition(|&b| b == b'"')
+ .expect("had a string without trailing \"");
+
+ s[pounds + 1..close].to_owned()
+}
+
+fn parse_lit_byte_str_cooked(mut v: &[u8]) -> Vec<u8> {
+ let mut out = Vec::new();
+ 'outer: loop {
+ let byte = match byte(v, 0) {
+ b'"' => break,
+ b'\\' => {
+ let b = byte(v, 1);
+ v = &v[2..];
+ match b {
+ b'x' => {
+ let (byte, rest) = backslash_x(v);
+ v = rest;
+ byte
+ }
+ b'n' => b'\n',
+ b'r' => b'\r',
+ b't' => b'\t',
+ b'\\' => b'\\',
+ b'0' => b'\0',
+ b'\'' => b'\'',
+ b'"' => b'"',
+ b'\r' | b'\n' => loop {
+ let byte = byte(v, 0);
+ let ch = char::from_u32(u32::from(byte)).expect("invalid byte");
+ if ch.is_whitespace() {
+ v = &v[1..];
+ } else {
+ continue 'outer;
+ }
+ },
+ _ => unreachable!("invalid escape"),
+ }
+ }
+ b'\r' => {
+ // bare CR not permitted
+ v = &v[2..];
+ b'\n'
+ }
+ b => {
+ v = &v[1..];
+ b
+ }
+ };
+ out.push(byte);
+ }
+
+ out
+}
+
+fn backslash_x<S>(s: &S) -> (u8, &S)
+where
+ S: Index<RangeFrom<usize>, Output = S> + AsRef<[u8]> + ?Sized,
+{
+ let mut ch = 0;
+ let b0 = byte(s, 0);
+ let b1 = byte(s, 1);
+ ch += 0x10 * (b0 - b'0');
+ ch += match b1 {
+ b'0'..=b'9' => b1 - b'0',
+ b'a'..=b'f' => 10 + (b1 - b'a'),
+ b'A'..=b'F' => 10 + (b1 - b'A'),
+ _ => unreachable!("invalid hex escape"),
+ };
+ (ch, &s[2..])
+}
+
+fn backslash_u(mut s: &str) -> (char, &str) {
+ s = &s[1..];
+
+ let mut ch = 0;
+ let mut digits = 0;
+ loop {
+ let b = byte(s, 0);
+ let digit = match b {
+ b'0'..=b'9' => b - b'0',
+ b'a'..=b'f' => 10 + b - b'a',
+ b'A'..=b'F' => 10 + b - b'A',
+ b'_' if digits > 0 => {
+ s = &s[1..];
+ continue;
+ }
+ b'}' if digits != 0 => break,
+ _ => unreachable!("invalid unicode escape"),
+ };
+ ch *= 0x10;
+ ch += u32::from(digit);
+ digits += 1;
+ s = &s[1..];
+ }
+ s = &s[1..];
+
+ (
+ char::from_u32(ch).expect("invalid unicode escape passed by compiler"),
+ s,
+ )
+}
diff --git a/third_party/rust/time-macros/src/lib.rs b/third_party/rust/time-macros/src/lib.rs
new file mode 100644
index 0000000000..1afc313eaf
--- /dev/null
+++ b/third_party/rust/time-macros/src/lib.rs
@@ -0,0 +1,167 @@
+#![deny(
+ anonymous_parameters,
+ clippy::all,
+ const_err,
+ illegal_floating_point_literal_pattern,
+ late_bound_lifetime_arguments,
+ path_statements,
+ patterns_in_fns_without_body,
+ rust_2018_idioms,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unsafe_code,
+ unused_extern_crates
+)]
+#![warn(
+ clippy::dbg_macro,
+ clippy::decimal_literal_representation,
+ clippy::get_unwrap,
+ clippy::nursery,
+ clippy::print_stdout,
+ clippy::todo,
+ clippy::unimplemented,
+ clippy::unnested_or_patterns,
+ clippy::unwrap_used,
+ clippy::use_debug,
+ single_use_lifetimes,
+ unused_qualifications,
+ variant_size_differences
+)]
+#![allow(
+ clippy::missing_const_for_fn, // useless in proc macro
+ clippy::redundant_pub_crate, // suggests bad style
+ clippy::option_if_let_else, // suggests terrible code
+)]
+
+#[macro_use]
+mod quote;
+
+mod date;
+mod datetime;
+mod error;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+mod format_description;
+mod helpers;
+mod offset;
+#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
+mod serde_format_description;
+mod time;
+mod to_tokens;
+
+use proc_macro::TokenStream;
+#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
+use proc_macro::TokenTree;
+
+use self::error::Error;
+
+macro_rules! impl_macros {
+ ($($name:ident)*) => {$(
+ #[proc_macro]
+ pub fn $name(input: TokenStream) -> TokenStream {
+ use crate::to_tokens::ToTokenTree;
+
+ let mut iter = input.into_iter().peekable();
+ match $name::parse(&mut iter) {
+ Ok(value) => match iter.peek() {
+ Some(tree) => Error::UnexpectedToken { tree: tree.clone() }.to_compile_error(),
+ None => TokenStream::from(value.into_token_tree()),
+ },
+ Err(err) => err.to_compile_error(),
+ }
+ }
+ )*};
+}
+
+impl_macros![date datetime offset time];
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+#[proc_macro]
+pub fn format_description(input: TokenStream) -> TokenStream {
+ (|| {
+ let (span, string) = helpers::get_string_literal(input)?;
+ let items = format_description::parse(&string, span)?;
+
+ Ok(quote! {{
+ const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S(
+ items
+ .into_iter()
+ .map(|item| quote! { #S(item), })
+ .collect::<TokenStream>()
+ )];
+ DESCRIPTION
+ }})
+ })()
+ .unwrap_or_else(|err: Error| err.to_compile_error())
+}
+
+#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
+#[proc_macro]
+pub fn serde_format_description(input: TokenStream) -> TokenStream {
+ (|| {
+ let mut tokens = input.into_iter().peekable();
+ // First, an identifier (the desired module name)
+ let mod_name = match tokens.next() {
+ Some(TokenTree::Ident(ident)) => Ok(ident),
+ Some(tree) => Err(Error::UnexpectedToken { tree }),
+ None => Err(Error::UnexpectedEndOfInput),
+ }?;
+
+ // Followed by a comma
+ helpers::consume_punct(',', &mut tokens)?;
+
+ // Then, the type to create serde serializers for (e.g., `OffsetDateTime`).
+ let formattable = match tokens.next() {
+ Some(tree @ TokenTree::Ident(_)) => Ok(tree),
+ Some(tree) => Err(Error::UnexpectedToken { tree }),
+ None => Err(Error::UnexpectedEndOfInput),
+ }?;
+
+ // Another comma
+ helpers::consume_punct(',', &mut tokens)?;
+
+ // We now have two options. The user can either provide a format description as a string or
+ // they can provide a path to a format description. If the latter, all remaining tokens are
+ // assumed to be part of the path.
+ let (format, raw_format_string) = match tokens.peek() {
+ // string literal
+ Some(TokenTree::Literal(_)) => {
+ let (span, format_string) = helpers::get_string_literal(tokens.collect())?;
+ let items = format_description::parse(&format_string, span)?;
+ let items: TokenStream =
+ items.into_iter().map(|item| quote! { #S(item), }).collect();
+ let items = quote! { &[#S(items)] };
+
+ (
+ items,
+ Some(String::from_utf8_lossy(&format_string).into_owned()),
+ )
+ }
+ // path
+ Some(_) => (
+ quote! {{
+ // We can't just do `super::path` because the path could be an absolute path. In
+ // that case, we'd be generating `super::::path`, which is invalid. Even if we
+ // took that into account, it's not possible to know if it's an external crate,
+ // which would just require emitting `path` directly. By taking this approach,
+ // we can leave it to the compiler to do the actual resolution.
+ mod __path_hack {
+ pub(super) use super::super::*;
+ pub(super) use #S(tokens.collect::<TokenStream>()) as FORMAT;
+ }
+ __path_hack::FORMAT
+ }},
+ None,
+ ),
+ None => return Err(Error::UnexpectedEndOfInput),
+ };
+
+ Ok(serde_format_description::build(
+ mod_name,
+ formattable,
+ format,
+ raw_format_string,
+ ))
+ })()
+ .unwrap_or_else(|err: Error| err.to_compile_error_standalone())
+}
diff --git a/third_party/rust/time-macros/src/offset.rs b/third_party/rust/time-macros/src/offset.rs
new file mode 100644
index 0000000000..c2099073f8
--- /dev/null
+++ b/third_party/rust/time-macros/src/offset.rs
@@ -0,0 +1,95 @@
+use std::iter::Peekable;
+
+use proc_macro::{token_stream, Span, TokenTree};
+
+use crate::helpers::{consume_any_ident, consume_number, consume_punct};
+use crate::to_tokens::ToTokenTree;
+use crate::Error;
+
+pub(crate) struct Offset {
+ pub(crate) hours: i8,
+ pub(crate) minutes: i8,
+ pub(crate) seconds: i8,
+}
+
+pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offset, Error> {
+ if consume_any_ident(&["utc", "UTC"], chars).is_ok() {
+ return Ok(Offset {
+ hours: 0,
+ minutes: 0,
+ seconds: 0,
+ });
+ }
+
+ let sign = if consume_punct('+', chars).is_ok() {
+ 1
+ } else if consume_punct('-', chars).is_ok() {
+ -1
+ } else if let Some(tree) = chars.next() {
+ return Err(Error::UnexpectedToken { tree });
+ } else {
+ return Err(Error::MissingComponent {
+ name: "sign",
+ span_start: None,
+ span_end: None,
+ });
+ };
+
+ let (hours_span, hours) = consume_number::<i8>("hour", chars)?;
+ let (mut minutes_span, mut minutes) = (Span::mixed_site(), 0);
+ let (mut seconds_span, mut seconds) = (Span::mixed_site(), 0);
+
+ if consume_punct(':', chars).is_ok() {
+ let min = consume_number::<i8>("minute", chars)?;
+ minutes_span = min.0;
+ minutes = min.1;
+
+ if consume_punct(':', chars).is_ok() {
+ let sec = consume_number::<i8>("second", chars)?;
+ seconds_span = sec.0;
+ seconds = sec.1;
+ }
+ }
+
+ if hours >= 24 {
+ Err(Error::InvalidComponent {
+ name: "hour",
+ value: hours.to_string(),
+ span_start: Some(hours_span),
+ span_end: Some(hours_span),
+ })
+ } else if minutes >= 60 {
+ Err(Error::InvalidComponent {
+ name: "minute",
+ value: minutes.to_string(),
+ span_start: Some(minutes_span),
+ span_end: Some(minutes_span),
+ })
+ } else if seconds >= 60 {
+ Err(Error::InvalidComponent {
+ name: "second",
+ value: seconds.to_string(),
+ span_start: Some(seconds_span),
+ span_end: Some(seconds_span),
+ })
+ } else {
+ Ok(Offset {
+ hours: sign * hours,
+ minutes: sign * minutes,
+ seconds: sign * seconds,
+ })
+ }
+}
+
+impl ToTokenTree for Offset {
+ fn into_token_tree(self) -> TokenTree {
+ quote_group! {{
+ const OFFSET: ::time::UtcOffset = ::time::UtcOffset::__from_hms_unchecked(
+ #(self.hours),
+ #(self.minutes),
+ #(self.seconds),
+ );
+ OFFSET
+ }}
+ }
+}
diff --git a/third_party/rust/time-macros/src/quote.rs b/third_party/rust/time-macros/src/quote.rs
new file mode 100644
index 0000000000..2fe86cc98d
--- /dev/null
+++ b/third_party/rust/time-macros/src/quote.rs
@@ -0,0 +1,134 @@
+macro_rules! quote {
+ () => (::proc_macro::TokenStream::new());
+ ($($x:tt)*) => {{
+ let mut ts = ::proc_macro::TokenStream::new();
+ let ts_mut = &mut ts;
+ quote_inner!(ts_mut $($x)*);
+ ts
+ }};
+}
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+macro_rules! quote_append {
+ ($ts:ident $($x:tt)*) => {{
+ quote_inner!($ts $($x)*);
+ }};
+}
+
+macro_rules! quote_group {
+ ({ $($x:tt)* }) => {
+ ::proc_macro::TokenTree::Group(::proc_macro::Group::new(
+ ::proc_macro::Delimiter::Brace,
+ quote!($($x)*)
+ ))
+ };
+}
+
+macro_rules! sym {
+ ($ts:ident $x:tt $y:tt) => {
+ $ts.extend([
+ ::proc_macro::TokenTree::from(::proc_macro::Punct::new(
+ $x,
+ ::proc_macro::Spacing::Joint,
+ )),
+ ::proc_macro::TokenTree::from(::proc_macro::Punct::new(
+ $y,
+ ::proc_macro::Spacing::Alone,
+ )),
+ ]);
+ };
+ ($ts:ident $x:tt) => {
+ $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Punct::new(
+ $x,
+ ::proc_macro::Spacing::Alone,
+ ))]);
+ };
+}
+
+macro_rules! quote_inner {
+ // Base case
+ ($ts:ident) => {};
+
+ // Single or double symbols
+ ($ts:ident :: $($tail:tt)*) => { sym!($ts ':' ':'); quote_inner!($ts $($tail)*); };
+ ($ts:ident .. $($tail:tt)*) => { sym!($ts '.' '.'); quote_inner!($ts $($tail)*); };
+ ($ts:ident : $($tail:tt)*) => { sym!($ts ':'); quote_inner!($ts $($tail)*); };
+ ($ts:ident = $($tail:tt)*) => { sym!($ts '='); quote_inner!($ts $($tail)*); };
+ ($ts:ident ; $($tail:tt)*) => { sym!($ts ';'); quote_inner!($ts $($tail)*); };
+ ($ts:ident , $($tail:tt)*) => { sym!($ts ','); quote_inner!($ts $($tail)*); };
+ ($ts:ident . $($tail:tt)*) => { sym!($ts '.'); quote_inner!($ts $($tail)*); };
+ ($ts:ident & $($tail:tt)*) => { sym!($ts '&'); quote_inner!($ts $($tail)*); };
+ ($ts:ident << $($tail:tt)*) => { sym!($ts '<' '<'); quote_inner!($ts $($tail)*); };
+ ($ts:ident < $($tail:tt)*) => { sym!($ts '<'); quote_inner!($ts $($tail)*); };
+ ($ts:ident >> $($tail:tt)*) => { sym!($ts '>' '>'); quote_inner!($ts $($tail)*); };
+ ($ts:ident > $($tail:tt)*) => { sym!($ts '>'); quote_inner!($ts $($tail)*); };
+ ($ts:ident -> $($tail:tt)*) => { sym!($ts '-' '>'); quote_inner!($ts $($tail)*); };
+ ($ts:ident ? $($tail:tt)*) => { sym!($ts '?'); quote_inner!($ts $($tail)*); };
+ ($ts:ident ! $($tail:tt)*) => { sym!($ts '!'); quote_inner!($ts $($tail)*); };
+ ($ts:ident | $($tail:tt)*) => { sym!($ts '|'); quote_inner!($ts $($tail)*); };
+ ($ts:ident * $($tail:tt)*) => { sym!($ts '*'); quote_inner!($ts $($tail)*); };
+
+ // Identifier
+ ($ts:ident $i:ident $($tail:tt)*) => {
+ $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Ident::new(
+ &stringify!($i),
+ ::proc_macro::Span::mixed_site(),
+ ))]);
+ quote_inner!($ts $($tail)*);
+ };
+
+ // Literal
+ ($ts:ident $l:literal $($tail:tt)*) => {
+ $ts.extend([::proc_macro::TokenTree::from(::proc_macro::Literal::string(&$l))]);
+ quote_inner!($ts $($tail)*);
+ };
+
+ // Lifetime
+ ($ts:ident $l:lifetime $($tail:tt)*) => {
+ $ts.extend([
+ ::proc_macro::TokenTree::from(
+ ::proc_macro::Punct::new('\'', ::proc_macro::Spacing::Joint)
+ ),
+ ::proc_macro::TokenTree::from(::proc_macro::Ident::new(
+ stringify!($l).trim_start_matches(|c| c == '\''),
+ ::proc_macro::Span::mixed_site(),
+ )),
+ ]);
+ quote_inner!($ts $($tail)*);
+ };
+
+ // Groups
+ ($ts:ident ($($inner:tt)*) $($tail:tt)*) => {
+ $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
+ ::proc_macro::Delimiter::Parenthesis,
+ quote!($($inner)*)
+ ))]);
+ quote_inner!($ts $($tail)*);
+ };
+ ($ts:ident [$($inner:tt)*] $($tail:tt)*) => {
+ $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
+ ::proc_macro::Delimiter::Bracket,
+ quote!($($inner)*)
+ ))]);
+ quote_inner!($ts $($tail)*);
+ };
+ ($ts:ident {$($inner:tt)*} $($tail:tt)*) => {
+ $ts.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
+ ::proc_macro::Delimiter::Brace,
+ quote!($($inner)*)
+ ))]);
+ quote_inner!($ts $($tail)*);
+ };
+
+ // Interpolated values
+ // TokenTree by default
+ ($ts:ident #($e:expr) $($tail:tt)*) => {
+ $ts.extend([$crate::to_tokens::ToTokenTree::into_token_tree($e)]);
+ quote_inner!($ts $($tail)*);
+ };
+ // Allow a TokenStream by request. It's more expensive, so avoid if possible.
+ ($ts:ident #S($e:expr) $($tail:tt)*) => {
+ $crate::to_tokens::ToTokenStream::append_to($e, $ts);
+ quote_inner!($ts $($tail)*);
+ };
+}
diff --git a/third_party/rust/time-macros/src/serde_format_description.rs b/third_party/rust/time-macros/src/serde_format_description.rs
new file mode 100644
index 0000000000..c09a4e9e2a
--- /dev/null
+++ b/third_party/rust/time-macros/src/serde_format_description.rs
@@ -0,0 +1,163 @@
+use proc_macro::{Ident, TokenStream, TokenTree};
+
+pub(crate) fn build(
+ mod_name: Ident,
+ ty: TokenTree,
+ format: TokenStream,
+ raw_format_string: Option<String>,
+) -> TokenStream {
+ let ty_s = &*ty.to_string();
+
+ let format_description_display = raw_format_string.unwrap_or_else(|| format.to_string());
+
+ let visitor = if cfg!(feature = "parsing") {
+ quote! {
+ struct Visitor;
+ struct OptionVisitor;
+
+ impl<'a> ::serde::de::Visitor<'a> for Visitor {
+ type Value = __TimeSerdeType;
+
+ fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(
+ f,
+ concat!(
+ "a(n) `",
+ #(ty_s),
+ "` in the format \"{}\"",
+ ),
+ #(format_description_display.as_str())
+ )
+ }
+
+ fn visit_str<E: ::serde::de::Error>(
+ self,
+ value: &str
+ ) -> Result<__TimeSerdeType, E> {
+ __TimeSerdeType::parse(value, &DESCRIPTION).map_err(E::custom)
+ }
+ }
+
+ impl<'a> ::serde::de::Visitor<'a> for OptionVisitor {
+ type Value = Option<__TimeSerdeType>;
+
+ fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(
+ f,
+ concat!(
+ "an `Option<",
+ #(ty_s),
+ ">` in the format \"{}\"",
+ ),
+ #(format_description_display.as_str())
+ )
+ }
+
+ fn visit_some<D: ::serde::de::Deserializer<'a>>(
+ self,
+ deserializer: D
+ ) -> Result<Option<__TimeSerdeType>, D::Error> {
+ deserializer
+ .deserialize_any(Visitor)
+ .map(Some)
+ }
+
+ fn visit_none<E: ::serde::de::Error>(
+ self
+ ) -> Result<Option<__TimeSerdeType>, E> {
+ Ok(None)
+ }
+ }
+ }
+ } else {
+ quote!()
+ };
+
+ let serialize_primary = if cfg!(feature = "formatting") {
+ quote! {
+ pub fn serialize<S: ::serde::Serializer>(
+ datetime: &__TimeSerdeType,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ use ::serde::Serialize;
+ datetime
+ .format(&DESCRIPTION)
+ .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
+ .serialize(serializer)
+ }
+ }
+ } else {
+ quote!()
+ };
+
+ let deserialize_primary = if cfg!(feature = "parsing") {
+ quote! {
+ pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
+ deserializer: D
+ ) -> Result<__TimeSerdeType, D::Error> {
+ use ::serde::Deserialize;
+ deserializer.deserialize_any(Visitor)
+ }
+ }
+ } else {
+ quote!()
+ };
+
+ let serialize_option = if cfg!(feature = "formatting") {
+ quote! {
+ pub fn serialize<S: ::serde::Serializer>(
+ option: &Option<__TimeSerdeType>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ use ::serde::Serialize;
+ option.map(|datetime| datetime.format(&DESCRIPTION))
+ .transpose()
+ .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
+ .serialize(serializer)
+ }
+ }
+ } else {
+ quote!()
+ };
+
+ let deserialize_option = if cfg!(feature = "parsing") {
+ quote! {
+ pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
+ deserializer: D
+ ) -> Result<Option<__TimeSerdeType>, D::Error> {
+ use ::serde::Deserialize;
+ deserializer.deserialize_option(OptionVisitor)
+ }
+ }
+ } else {
+ quote!()
+ };
+
+ let deserialize_option_imports = if cfg!(feature = "parsing") {
+ quote! {
+ use super::{OptionVisitor, Visitor};
+ }
+ } else {
+ quote!()
+ };
+
+ quote! {
+ mod #(mod_name) {
+ use ::time::#(ty) as __TimeSerdeType;
+
+ const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = #S(format);
+
+ #S(visitor)
+ #S(serialize_primary)
+ #S(deserialize_primary)
+
+ pub(super) mod option {
+ use super::{DESCRIPTION, __TimeSerdeType};
+ #S(deserialize_option_imports)
+
+ #S(serialize_option)
+ #S(deserialize_option)
+ }
+ }
+ }
+}
diff --git a/third_party/rust/time-macros/src/time.rs b/third_party/rust/time-macros/src/time.rs
new file mode 100644
index 0000000000..719e2051f1
--- /dev/null
+++ b/third_party/rust/time-macros/src/time.rs
@@ -0,0 +1,118 @@
+use std::iter::Peekable;
+
+use proc_macro::{token_stream, Span, TokenTree};
+
+use crate::helpers::{consume_any_ident, consume_number, consume_punct};
+use crate::to_tokens::ToTokenTree;
+use crate::Error;
+
+enum Period {
+ Am,
+ Pm,
+ _24,
+}
+
+pub(crate) struct Time {
+ pub(crate) hour: u8,
+ pub(crate) minute: u8,
+ pub(crate) second: u8,
+ pub(crate) nanosecond: u32,
+}
+
+pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time, Error> {
+ fn consume_period(chars: &mut Peekable<token_stream::IntoIter>) -> (Option<Span>, Period) {
+ if let Ok(span) = consume_any_ident(&["am", "AM"], chars) {
+ (Some(span), Period::Am)
+ } else if let Ok(span) = consume_any_ident(&["pm", "PM"], chars) {
+ (Some(span), Period::Pm)
+ } else {
+ (None, Period::_24)
+ }
+ }
+
+ let (hour_span, hour) = consume_number("hour", chars)?;
+
+ let ((minute_span, minute), (second_span, second), (period_span, period)) =
+ match consume_period(chars) {
+ // Nothing but the 12-hour clock hour and AM/PM
+ (period_span @ Some(_), period) => (
+ (Span::mixed_site(), 0),
+ (Span::mixed_site(), 0.),
+ (period_span, period),
+ ),
+ (None, _) => {
+ consume_punct(':', chars)?;
+ let (minute_span, minute) = consume_number::<u8>("minute", chars)?;
+ let (second_span, second): (_, f64) = if consume_punct(':', chars).is_ok() {
+ consume_number("second", chars)?
+ } else {
+ (Span::mixed_site(), 0.)
+ };
+ let (period_span, period) = consume_period(chars);
+ (
+ (minute_span, minute),
+ (second_span, second),
+ (period_span, period),
+ )
+ }
+ };
+
+ let hour = match (hour, period) {
+ (0, Period::Am | Period::Pm) => {
+ return Err(Error::InvalidComponent {
+ name: "hour",
+ value: hour.to_string(),
+ span_start: Some(hour_span),
+ span_end: Some(period_span.unwrap_or(hour_span)),
+ });
+ }
+ (12, Period::Am) => 0,
+ (12, Period::Pm) => 12,
+ (hour, Period::Am | Period::_24) => hour,
+ (hour, Period::Pm) => hour + 12,
+ };
+
+ if hour >= 24 {
+ Err(Error::InvalidComponent {
+ name: "hour",
+ value: hour.to_string(),
+ span_start: Some(hour_span),
+ span_end: Some(period_span.unwrap_or(hour_span)),
+ })
+ } else if minute >= 60 {
+ Err(Error::InvalidComponent {
+ name: "minute",
+ value: minute.to_string(),
+ span_start: Some(minute_span),
+ span_end: Some(minute_span),
+ })
+ } else if second >= 60. {
+ Err(Error::InvalidComponent {
+ name: "second",
+ value: second.to_string(),
+ span_start: Some(second_span),
+ span_end: Some(second_span),
+ })
+ } else {
+ Ok(Time {
+ hour,
+ minute,
+ second: second.trunc() as _,
+ nanosecond: (second.fract() * 1_000_000_000.).round() as _,
+ })
+ }
+}
+
+impl ToTokenTree for Time {
+ fn into_token_tree(self) -> TokenTree {
+ quote_group! {{
+ const TIME: ::time::Time = ::time::Time::__from_hms_nanos_unchecked(
+ #(self.hour),
+ #(self.minute),
+ #(self.second),
+ #(self.nanosecond),
+ );
+ TIME
+ }}
+ }
+}
diff --git a/third_party/rust/time-macros/src/to_tokens.rs b/third_party/rust/time-macros/src/to_tokens.rs
new file mode 100644
index 0000000000..3a293925c9
--- /dev/null
+++ b/third_party/rust/time-macros/src/to_tokens.rs
@@ -0,0 +1,68 @@
+use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
+
+pub(crate) trait ToTokenStream: Sized {
+ fn append_to(self, ts: &mut TokenStream);
+}
+
+pub(crate) trait ToTokenTree: Sized {
+ fn into_token_tree(self) -> TokenTree;
+}
+
+impl<T: ToTokenTree> ToTokenStream for T {
+ fn append_to(self, ts: &mut TokenStream) {
+ ts.extend([self.into_token_tree()])
+ }
+}
+
+impl ToTokenTree for bool {
+ fn into_token_tree(self) -> TokenTree {
+ let lit = if self { "true" } else { "false" };
+ TokenTree::Ident(Ident::new(lit, Span::mixed_site()))
+ }
+}
+
+impl ToTokenStream for TokenStream {
+ fn append_to(self, ts: &mut TokenStream) {
+ ts.extend(self)
+ }
+}
+
+impl ToTokenTree for TokenTree {
+ fn into_token_tree(self) -> TokenTree {
+ self
+ }
+}
+
+impl ToTokenTree for &str {
+ fn into_token_tree(self) -> TokenTree {
+ TokenTree::Literal(Literal::string(self))
+ }
+}
+
+macro_rules! impl_for_tree_types {
+ ($($type:ty)*) => {$(
+ impl ToTokenTree for $type {
+ fn into_token_tree(self) -> TokenTree {
+ TokenTree::from(self)
+ }
+ }
+ )*};
+}
+impl_for_tree_types![Ident Literal Group Punct];
+
+macro_rules! impl_for_int {
+ ($($type:ty => $method:ident)*) => {$(
+ impl ToTokenTree for $type {
+ fn into_token_tree(self) -> TokenTree {
+ TokenTree::from(Literal::$method(self))
+ }
+ }
+ )*};
+}
+impl_for_int! {
+ i8 => i8_unsuffixed
+ u8 => u8_unsuffixed
+ u16 => u16_unsuffixed
+ i32 => i32_unsuffixed
+ u32 => u32_unsuffixed
+}
diff --git a/third_party/rust/time/.cargo-checksum.json b/third_party/rust/time/.cargo-checksum.json
new file mode 100644
index 0000000000..cb92bb424b
--- /dev/null
+++ b/third_party/rust/time/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"9310f48e6d4dc1bae156b3bc0b8cbbcfe0069f5f3724e8ce1840589214631763","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","README.md":"255d27d2cbd3668cadc6ad6e2b5ad3ae737e3fdba90e5a2ec7c538e94a9a4ff6","src/date.rs":"609cab5429ee0141a6d475f4548b6ec166ef512d79b283c149f415a82f9f1579","src/duration.rs":"bc566d667adb286f190fd125c4c4d034bb02702f02b8077be05b81deb95f22c7","src/error/component_range.rs":"26a1aa4ea2d0f9887efcbe9584d5aa14b1e5d37525a52dc9f18e1e282599625d","src/error/conversion_range.rs":"972abb765370070de01e2fc2e1bb1e80808a069e6213577d7beaca02e1d707c3","src/error/different_variant.rs":"107bef7b3addd7108b36a2da8389f611d4482f34a5b63429841141e05c8cb30c","src/error/format.rs":"d87846c2ac62dec421402ea21e5d2a8d73add6658df4ac914067a4b43cb0ef20","src/error/indeterminate_offset.rs":"1f52f9ea107847fa781399cfcc8046451d70155fb497486c80b2138f82782941","src/error/invalid_format_description.rs":"c36f873d8823ee6aa3eded98fdf9f3c71572e24eb3c8e8a584bbb2195ee27c2d","src/error/invalid_variant.rs":"b653a3e6e902f06cb9f2e0366c4da84b92e8bdb03164c2f8cb15fe66415706e4","src/error/mod.rs":"15fb848b1919d9cfb50fb9091abfcea6a8c7db5a2fcd6cb8f32c4af5f1ea4464","src/error/parse.rs":"3bdc8201a14469d2cc7a12a295058569098f9cfc9bd1e8fc9f526ada8298e4f8","src/error/parse_from_description.rs":"990359eb5fcb64c1ee363b044147b7330a92a4cb7373dc2f17f6fd3bcc6411a0","src/error/try_from_parsed.rs":"8c227be52653a1d33af01a8024c0fc56f1f9803f08ef01487a7eaa5833adbb57","src/ext.rs":"03e08906d328f18701237eccd3e220dfa82bc21290ff386b8440588d30dd192b","src/format_description/borrowed_format_item.rs":"afab66e65a84895751d3557fc5b8a3a5e63f9c483a6a534aa4f86fd2a5145f0b","src/format_description/component.rs":"8e39e93bf27c53f4b1debdc427741621ea6503c0343a6c762316069e1b7a82cc","src/format_description/mod.rs":"d3c53d6e8d183cd9168204b58df075ad1118780aa127b04480e1c351979edbf5","src/format_description/modifier.rs":"b013c71c59977b51d989b48adc7758a40fc9d05511c13070eb3849cefd2ab46a","src/format_description/owned_format_item.rs":"419f5354bf504562c9225dfe90b61eee9bc959211a86a327197b4f54283da775","src/format_description/parse/ast.rs":"a3614bc4206d8d09824f4f98dd29bed2c90fa6336510bde3b087f8880e925dc1","src/format_description/parse/format_item.rs":"d00246f892a7e88fe017b29bb757cd2b98631b97cc87749db41ce48781fd50ee","src/format_description/parse/lexer.rs":"0390142bf16a242d6eb82c19ec9807a8a3c1e05a629bdddda1a68c0c6eabad9f","src/format_description/parse/mod.rs":"3942c8248cc84fb45cae7b0b2552e47b95638235a799002b8932a26906769b5b","src/format_description/well_known/iso8601.rs":"8313905039a637d4d132f8318a59c06246e7b61550b4e4bc7d129232ac022e43","src/format_description/well_known/iso8601/adt_hack.rs":"82c308ea2f7ae87f7cc9e01e931cdf1633d89bf0002403b66cb041059d6f5873","src/format_description/well_known/rfc2822.rs":"36c23394724ae12250d4193cab26887a6ff8f82ca441ea6b0d03c4f1c928b3dd","src/format_description/well_known/rfc3339.rs":"1a6318dffd3ebb6ac7cf96eae3d9b1eb44b1089cf4284fa6a7e935c6fcf1b43c","src/formatting/formattable.rs":"a95d0b4f6e083bc061ae915391e7990b07e861aca9913e75595856d95178ce6f","src/formatting/iso8601.rs":"dda61b56e8d33071d9cb6e70dca61de89c2e4abc555bdbdfc1ad72ad3c0611ac","src/formatting/mod.rs":"cbfed6ee8a21d3832b34b1ba168d750ffa0927a71bf813fc8c4032a72f6a17fd","src/instant.rs":"15f8d497d71730a9bbd2103992eec8f626e2f632486564fdd641a72c4e20a21d","src/lib.rs":"d5bb5e86a350b64e0a92f5630b28c54692030145abe389a3abfe3e7499ccbdce","src/macros.rs":"eb9e02a1f97bb8befab7bc27c937136817e4f65e0b3e040a81394ae938980558","src/month.rs":"a9fdc0bc4c8f668a69edb8e51ea2c0f48ac801ace0a7332abb6983282b2fba43","src/offset_date_time.rs":"83935b393f31e15c82139ceca465834c4319ef7895f93c294679d4977530fa82","src/parsing/combinator/mod.rs":"b342fbd95dd986309d81e8910363920ba6db00958b459f6d97f57da3ae3e550d","src/parsing/combinator/rfc/iso8601.rs":"13289a0d58de273327830a3001167a8964edc5045486301efdf3ddc2e4079c32","src/parsing/combinator/rfc/mod.rs":"f30b75d248f5ae92c27646d504703f5489185afb76c998cc4375437b3d15c822","src/parsing/combinator/rfc/rfc2234.rs":"08e2813c6d40c0dae881875fe0417ae06886c73679256587e33186e46b3c3bae","src/parsing/combinator/rfc/rfc2822.rs":"2aff3a6a2778bc806031cff92ad2f43f0874620b5d484b5b39ee2d2507212f06","src/parsing/component.rs":"067d9ee045eb4424f213cf0e48c571d2c8d80839373bbf4bab575942d8dbc28c","src/parsing/iso8601.rs":"30c49e23939d827decfd01c223425c73085d32eaddd397d181652a1a210f442e","src/parsing/mod.rs":"37082ac824c6c3f4900766a0a3140dc7aa46b3f85cb6098f11da7da333e421b0","src/parsing/parsable.rs":"47c99751470e1334233b3fe67161ff61e9c6e63dad4c66273e1a378164bf2084","src/parsing/parsed.rs":"3b2adbf6b076b730e38ee23aa034b0061ae40a3d660f877a7227b56ed50cdc2e","src/parsing/shim.rs":"800be565765a317f4515e598b37d69850e1f90a89ff16e3650a9fd92a58239fe","src/primitive_date_time.rs":"187932764c7a9b0f551d3c4dce832f1f8d8560c3aaa35e0a09356b744174c68c","src/quickcheck.rs":"eb0b4ae369d9fdba0ddb9d3a30b6f4179d9f123513d2cbd5a55962c89bee8ef2","src/rand.rs":"ebee80c9d4301229eef91cb0574eeecaf1157d7cf806dece0bb202ee8af2bda7","src/serde/iso8601.rs":"d77e588d4ede08e4e5ddb3a281184802fdc80ce3f263a1b946871fbc13162633","src/serde/mod.rs":"b1c0b69974d33ccd55c4080647f3fa0819f2a9df7299e3dc8700fe6cd338c704","src/serde/rfc2822.rs":"fe97aa1311037a362eb477fe8c6729b3b85ff2d0afab7148f10f64d109081f90","src/serde/rfc3339.rs":"9835c8b8fb24b53657769b81a71188fe4261e5869917779e1702b3a0aa854654","src/serde/timestamp.rs":"30971ad5d1fef11e396eee48d476b828ed4e99f6eac587383b864dd95c120fe4","src/serde/visitor.rs":"6a2a10cfe5afa59f7c8f02585c589514a9fbafdac538b2557a0571f00a0858b7","src/sys/local_offset_at/imp.rs":"4b6e57f02566364270ac9b7e1540290a5658a296f7e911f988264d103e420326","src/sys/local_offset_at/mod.rs":"501a5d07f6f8852d72aeab6d1aae3630de149cbb182bf10184fb6764a46e5baa","src/sys/local_offset_at/unix.rs":"18aa655a365148f0bd833840c36ee4f50df0b31d83f66d6e6c2b7a4b0aabecef","src/sys/local_offset_at/wasm_js.rs":"7f6112bf7fd702b546189aca8fbcdb51f63b7e5d3af9d37cb95242aa07d989b1","src/sys/local_offset_at/windows.rs":"35919ea5b0533ca94910875043a01b6dac8f8cfc77a8c3e8f36cc7ced0a77aa1","src/sys/mod.rs":"0a43797e55e986233a71f1cc4b3a21997da42bc15db7d912373296cd535e49bc","src/tests.rs":"b2f6ca74f4b688a464fb55bb0e6a2e9033f393daff3b44121a6fd01063594b07","src/time.rs":"f985e0cc4dfaa87d45a7e73eb4b1331f193c7957823eb35f30f4d024cc1384a3","src/utc_offset.rs":"30517bc224b5d01a17f11856f8b27f38d7a371a60b06fa33071af7bb00db3a7f","src/util.rs":"d84521c576b236c4b86b7798532b02e38261dfed8790ef435e11a68fe9beed7c","src/weekday.rs":"2ee21c78f6de7cd5db50affb06da4f78fbe84e03007402c8919920734eca10c7"},"package":"a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"} \ No newline at end of file
diff --git a/third_party/rust/time/Cargo.toml b/third_party/rust/time/Cargo.toml
new file mode 100644
index 0000000000..8ff2173f69
--- /dev/null
+++ b/third_party/rust/time/Cargo.toml
@@ -0,0 +1,166 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.60.0"
+name = "time"
+version = "0.3.17"
+authors = [
+ "Jacob Pratt <open-source@jhpratt.dev>",
+ "Time contributors",
+]
+include = [
+ "src/**/*",
+ "LICENSE-*",
+ "README.md",
+]
+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 = [
+ "--cfg",
+ "__time_03_docs",
+]
+
+[lib]
+bench = false
+
+[[test]]
+name = "tests"
+path = "../tests/main.rs"
+
+[[bench]]
+name = "benchmarks"
+path = "../benchmarks/main.rs"
+harness = false
+
+[dependencies.itoa]
+version = "1.0.1"
+optional = true
+
+[dependencies.quickcheck]
+version = "1.0.3"
+optional = true
+default-features = false
+
+[dependencies.rand]
+version = "0.8.4"
+optional = true
+default-features = false
+
+[dependencies.serde]
+version = "1.0.126"
+optional = true
+default-features = false
+
+[dependencies.time-core]
+version = "=0.1.0"
+
+[dependencies.time-macros]
+version = "=0.2.6"
+optional = true
+
+[dev-dependencies.quickcheck_macros]
+version = "1.0.0"
+
+[dev-dependencies.rand]
+version = "0.8.4"
+default-features = false
+
+[dev-dependencies.serde]
+version = "1.0.126"
+features = ["derive"]
+default-features = false
+
+[dev-dependencies.serde_json]
+version = "1.0.68"
+
+[dev-dependencies.serde_test]
+version = "1.0.126"
+
+[dev-dependencies.time-macros]
+version = "=0.2.6"
+
+[features]
+alloc = ["serde?/alloc"]
+default = ["std"]
+formatting = [
+ "dep:itoa",
+ "std",
+ "time-macros?/formatting",
+]
+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",
+]
+rand = ["dep:rand"]
+serde = [
+ "dep:serde",
+ "time-macros?/serde",
+]
+serde-human-readable = [
+ "serde",
+ "formatting",
+ "parsing",
+]
+serde-well-known = [
+ "serde",
+ "formatting",
+ "parsing",
+]
+std = ["alloc"]
+wasm-bindgen = ["dep:js-sys"]
+
+[target."cfg(__ui_tests)".dev-dependencies.trybuild]
+version = "1.0.68"
+
+[target."cfg(all(target_arch = \"wasm32\", 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.4.0"
+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
diff --git a/third_party/rust/time/LICENSE-Apache b/third_party/rust/time/LICENSE-Apache
new file mode 100644
index 0000000000..7646f21e37
--- /dev/null
+++ b/third_party/rust/time/LICENSE-Apache
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2022 Jacob Pratt et al.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/rust/time/LICENSE-MIT b/third_party/rust/time/LICENSE-MIT
new file mode 100644
index 0000000000..a11a755732
--- /dev/null
+++ b/third_party/rust/time/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright (c) 2022 Jacob Pratt et al.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/rust/time/README.md b/third_party/rust/time/README.md
new file mode 100644
index 0000000000..34b5753408
--- /dev/null
+++ b/third_party/rust/time/README.md
@@ -0,0 +1,42 @@
+# time
+
+[![minimum rustc: 1.60](https://img.shields.io/badge/minimum%20rustc-1.60-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/workflow/status/time-rs/time/Build/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
+
+The time crate is guaranteed to compile with any release of rustc from the past six months.
+Optional feature flags that enable interoperability with third-party crates (e.g. rand)
+follow the policy of that crate if stricter.
+
+## 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.
diff --git a/third_party/rust/time/src/date.rs b/third_party/rust/time/src/date.rs
new file mode 100644
index 0000000000..b3023e0c05
--- /dev/null
+++ b/third_party/rust/time/src/date.rs
@@ -0,0 +1,1050 @@
+//! The [`Date`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year};
+use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday};
+
+/// The minimum valid year.
+pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") {
+ -999_999
+} else {
+ -9999
+};
+/// The maximum valid year.
+pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates") {
+ 999_999
+} else {
+ 9999
+};
+
+/// Date in the proleptic Gregorian calendar.
+///
+/// By default, years between ±9999 inclusive are representable. This can be expanded to ±999,999
+/// inclusive by enabling the `large-dates` crate feature. Doing so has performance implications
+/// and introduces some ambiguities when parsing.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Date {
+ /// Bitpacked field containing both the year and ordinal.
+ // | xx | xxxxxxxxxxxxxxxxxxxxx | xxxxxxxxx |
+ // | 2 bits | 21 bits | 9 bits |
+ // | unassigned | year | ordinal |
+ // The year is 15 bits when `large-dates` is not enabled.
+ value: i32,
+}
+
+impl Date {
+ /// The minimum valid `Date`.
+ ///
+ /// The value of this may vary depending on the feature flags enabled.
+ pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1);
+
+ /// The maximum valid `Date`.
+ ///
+ /// The value of this may vary depending on the feature flags enabled.
+ pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR));
+
+ // region: constructors
+ /// Construct a `Date` from the year and ordinal values, the validity of which must be
+ /// guaranteed by the caller.
+ #[doc(hidden)]
+ pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
+ debug_assert!(year >= MIN_YEAR);
+ debug_assert!(year <= MAX_YEAR);
+ debug_assert!(ordinal != 0);
+ debug_assert!(ordinal <= days_in_year(year));
+
+ Self {
+ value: (year << 9) | ordinal as i32,
+ }
+ }
+
+ /// Attempt to create a `Date` from the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::{Date, Month};
+ /// assert!(Date::from_calendar_date(2019, Month::January, 1).is_ok());
+ /// assert!(Date::from_calendar_date(2019, Month::December, 31).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::{Date, Month};
+ /// assert!(Date::from_calendar_date(2019, Month::February, 29).is_err()); // 2019 isn't a leap year.
+ /// ```
+ pub const fn from_calendar_date(
+ year: i32,
+ month: Month,
+ day: u8,
+ ) -> Result<Self, error::ComponentRange> {
+ /// Cumulative days through the beginning of a month in both common and leap years.
+ const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [
+ [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+ [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335],
+ ];
+
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month));
+
+ Ok(Self::__from_ordinal_date_unchecked(
+ year,
+ DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ + day as u16,
+ ))
+ }
+
+ /// Attempt to create a `Date` from the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// assert!(Date::from_ordinal_date(2019, 1).is_ok());
+ /// assert!(Date::from_ordinal_date(2019, 365).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year.
+ /// ```
+ pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year));
+ Ok(Self::__from_ordinal_date_unchecked(year, ordinal))
+ }
+
+ /// Attempt to create a `Date` from the ISO year, week, and weekday.
+ ///
+ /// ```rust
+ /// # use time::{Date, Weekday::*};
+ /// assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok());
+ /// assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok());
+ /// assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::{Date, Weekday::*};
+ /// assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks.
+ /// ```
+ pub const fn from_iso_week_date(
+ year: i32,
+ week: u8,
+ weekday: Weekday,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year));
+
+ let adj_year = year - 1;
+ let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
+ + div_floor!(adj_year, 400);
+ let jan_4 = match (raw % 7) as i8 {
+ -6 | 1 => 8,
+ -5 | 2 => 9,
+ -4 | 3 => 10,
+ -3 | 4 => 4,
+ -2 | 5 => 5,
+ -1 | 6 => 6,
+ _ => 7,
+ };
+ let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4;
+
+ Ok(if ordinal <= 0 {
+ Self::__from_ordinal_date_unchecked(
+ year - 1,
+ (ordinal as u16).wrapping_add(days_in_year(year - 1)),
+ )
+ } else if ordinal > days_in_year(year) as i16 {
+ Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
+ } else {
+ Self::__from_ordinal_date_unchecked(year, ordinal as _)
+ })
+ }
+
+ /// Create a `Date` from the Julian day.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24)));
+ /// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01)));
+ /// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01)));
+ /// assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31)));
+ /// ```
+ #[doc(alias = "from_julian_date")]
+ pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(
+ julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day()
+ );
+ Ok(Self::from_julian_day_unchecked(julian_day))
+ }
+
+ /// Create a `Date` from the Julian day.
+ ///
+ /// This does not check the validity of the provided Julian day, and as such may result in an
+ /// internally invalid value.
+ #[doc(alias = "from_julian_date_unchecked")]
+ pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self {
+ debug_assert!(julian_day >= Self::MIN.to_julian_day());
+ debug_assert!(julian_day <= Self::MAX.to_julian_day());
+
+ // To avoid a potential overflow, the value may need to be widened for some arithmetic.
+
+ let z = julian_day - 1_721_119;
+ let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 {
+ let g = 100 * z as i64 - 25;
+ let a = (g / 3_652_425) as i32;
+ let b = a - a / 4;
+ let year = div_floor!(100 * b as i64 + g, 36525) as i32;
+ let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _;
+ (year, ordinal)
+ } else {
+ let g = 100 * z - 25;
+ let a = g / 3_652_425;
+ let b = a - a / 4;
+ let year = div_floor!(100 * b + g, 36525);
+ let ordinal = (b + z - div_floor!(36525 * year, 100)) as _;
+ (year, ordinal)
+ };
+
+ if is_leap_year(year) {
+ ordinal += 60;
+ cascade!(ordinal in 1..367 => year);
+ } else {
+ ordinal += 59;
+ cascade!(ordinal in 1..366 => year);
+ }
+
+ Self::__from_ordinal_date_unchecked(year, ordinal)
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the year of the date.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).year(), 2019);
+ /// assert_eq!(date!(2019 - 12 - 31).year(), 2019);
+ /// assert_eq!(date!(2020 - 01 - 01).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.value >> 9
+ }
+
+ /// Get the month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).month(), Month::January);
+ /// assert_eq!(date!(2019 - 12 - 31).month(), Month::December);
+ /// ```
+ pub const fn month(self) -> Month {
+ self.month_day().0
+ }
+
+ /// Get the day of the month.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).day(), 1);
+ /// assert_eq!(date!(2019 - 12 - 31).day(), 31);
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.month_day().1
+ }
+
+ /// Get the month and day. This is more efficient than fetching the components individually.
+ // For whatever reason, rustc has difficulty optimizing this function. It's significantly faster
+ // to write the statements out by hand.
+ pub(crate) const fn month_day(self) -> (Month, u8) {
+ /// The number of days up to and including the given month. Common years
+ /// are first, followed by leap years.
+ const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 11]; 2] = [
+ [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+ [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335],
+ ];
+
+ let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(self.year()) as usize];
+ let ordinal = self.ordinal();
+
+ if ordinal > days[10] {
+ (Month::December, (ordinal - days[10]) as _)
+ } else if ordinal > days[9] {
+ (Month::November, (ordinal - days[9]) as _)
+ } else if ordinal > days[8] {
+ (Month::October, (ordinal - days[8]) as _)
+ } else if ordinal > days[7] {
+ (Month::September, (ordinal - days[7]) as _)
+ } else if ordinal > days[6] {
+ (Month::August, (ordinal - days[6]) as _)
+ } else if ordinal > days[5] {
+ (Month::July, (ordinal - days[5]) as _)
+ } else if ordinal > days[4] {
+ (Month::June, (ordinal - days[4]) as _)
+ } else if ordinal > days[3] {
+ (Month::May, (ordinal - days[3]) as _)
+ } else if ordinal > days[2] {
+ (Month::April, (ordinal - days[2]) as _)
+ } else if ordinal > days[1] {
+ (Month::March, (ordinal - days[1]) as _)
+ } else if ordinal > days[0] {
+ (Month::February, (ordinal - days[0]) as _)
+ } else {
+ (Month::January, ordinal as _)
+ }
+ }
+
+ /// Get the day of the year.
+ ///
+ /// The returned value will always be in the range `1..=366` (`1..=365` for common years).
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1);
+ /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365);
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ (self.value & 0x1FF) as _
+ }
+
+ /// Get the ISO 8601 year and week number.
+ pub(crate) const fn iso_year_week(self) -> (i32, u8) {
+ let (year, ordinal) = self.to_ordinal_date();
+
+ match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ {
+ 0 => (year - 1, weeks_in_year(year - 1)),
+ 53 if weeks_in_year(year) == 52 => (year + 1, 1),
+ week => (year, week),
+ }
+ }
+
+ /// Get the ISO week number.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1);
+ /// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40);
+ /// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1);
+ /// assert_eq!(date!(2020 - 12 - 31).iso_week(), 53);
+ /// assert_eq!(date!(2021 - 01 - 01).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.iso_year_week().1
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52);
+ /// assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0);
+ /// ```
+ pub const fn sunday_based_week(self) -> u8 {
+ ((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as _
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52);
+ /// assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0);
+ /// ```
+ pub const fn monday_based_week(self) -> u8 {
+ ((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as _
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 01).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ let (month, day) = self.month_day();
+ (self.year(), month, day)
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1));
+ /// ```
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ (self.year(), self.ordinal())
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday));
+ /// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday));
+ /// assert_eq!(
+ /// date!(2020 - 01 - 01).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday));
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ let (year, ordinal) = self.to_ordinal_date();
+ let weekday = self.weekday();
+
+ match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ {
+ 0 => (year - 1, weeks_in_year(year - 1), weekday),
+ 53 if weeks_in_year(year) == 52 => (year + 1, 1, weekday),
+ week => (year, week, weekday),
+ }
+ }
+
+ /// Get the weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday);
+ /// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 04 - 01).weekday(), Monday);
+ /// assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday);
+ /// assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday);
+ /// assert_eq!(date!(2019 - 07 - 01).weekday(), Monday);
+ /// assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday);
+ /// assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday);
+ /// assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday);
+ /// assert_eq!(date!(2019 - 11 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ match self.to_julian_day() % 7 {
+ -6 | 1 => Weekday::Tuesday,
+ -5 | 2 => Weekday::Wednesday,
+ -4 | 3 => Weekday::Thursday,
+ -3 | 4 => Weekday::Friday,
+ -2 | 5 => Weekday::Saturday,
+ -1 | 6 => Weekday::Sunday,
+ val => {
+ debug_assert!(val == 0);
+ Weekday::Monday
+ }
+ }
+ }
+
+ /// Get the next calendar date.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 01).next_day(),
+ /// Some(date!(2019 - 01 - 02))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 01 - 31).next_day(),
+ /// Some(date!(2019 - 02 - 01))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 12 - 31).next_day(),
+ /// Some(date!(2020 - 01 - 01))
+ /// );
+ /// assert_eq!(Date::MAX.next_day(), None);
+ /// ```
+ pub const fn next_day(self) -> Option<Self> {
+ if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) {
+ if self.value == Self::MAX.value {
+ None
+ } else {
+ Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1))
+ }
+ } else {
+ Some(Self {
+ value: self.value + 1,
+ })
+ }
+ }
+
+ /// Get the previous calendar date.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 02).previous_day(),
+ /// Some(date!(2019 - 01 - 01))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 02 - 01).previous_day(),
+ /// Some(date!(2019 - 01 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 01 - 01).previous_day(),
+ /// Some(date!(2019 - 12 - 31))
+ /// );
+ /// assert_eq!(Date::MIN.previous_day(), None);
+ /// ```
+ pub const fn previous_day(self) -> Option<Self> {
+ if self.ordinal() != 1 {
+ Some(Self {
+ value: self.value - 1,
+ })
+ } else if self.value == Self::MIN.value {
+ None
+ } else {
+ Some(Self::__from_ordinal_date_unchecked(
+ self.year() - 1,
+ days_in_year(self.year() - 1),
+ ))
+ }
+ }
+
+ /// Get the Julian day for the date.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0);
+ /// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545);
+ /// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485);
+ /// assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849);
+ /// ```
+ pub const fn to_julian_day(self) -> i32 {
+ let year = self.year() - 1;
+ let ordinal = self.ordinal() as i32;
+
+ ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100)
+ + div_floor!(year, 400)
+ + 1_721_425
+ }
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_add(1.days()), None);
+ /// assert_eq!(Date::MIN.checked_add((-2).days()), None);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(2.days()),
+ /// Some(date!(2021 - 01 - 02))
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX));
+ /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN));
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(23.hours()),
+ /// Some(date!(2020 - 12 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(47.hours()),
+ /// Some(date!(2021 - 01 - 01))
+ /// );
+ /// ```
+ pub const fn checked_add(self, duration: Duration) -> Option<Self> {
+ let whole_days = duration.whole_days();
+ if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 {
+ return None;
+ }
+
+ let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _));
+ if let Ok(date) = Self::from_julian_day(julian_day) {
+ Some(date)
+ } else {
+ None
+ }
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_sub((-2).days()), None);
+ /// assert_eq!(Date::MIN.checked_sub(1.days()), None);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(2.days()),
+ /// Some(date!(2020 - 12 - 29))
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX));
+ /// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN));
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(23.hours()),
+ /// Some(date!(2020 - 12 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(47.hours()),
+ /// Some(date!(2020 - 12 - 30))
+ /// );
+ /// ```
+ pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
+ let whole_days = duration.whole_days();
+ if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 {
+ return None;
+ }
+
+ let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _));
+ if let Ok(date) = Self::from_julian_day(julian_day) {
+ Some(date)
+ } else {
+ None
+ }
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX);
+ /// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(2.days()),
+ /// date!(2021 - 01 - 02)
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(23.hours()),
+ /// date!(2020 - 12 - 31)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(47.hours()),
+ /// date!(2021 - 01 - 01)
+ /// );
+ /// ```
+ pub const fn saturating_add(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_add(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self::MIN
+ } else {
+ debug_assert!(duration.is_positive());
+ Self::MAX
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX);
+ /// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(2.days()),
+ /// date!(2020 - 12 - 29)
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(23.hours()),
+ /// date!(2020 - 12 - 31)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(47.hours()),
+ /// date!(2020 - 12 - 30)
+ /// );
+ /// ```
+ pub const fn saturating_sub(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_sub(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self::MAX
+ } else {
+ debug_assert!(duration.is_positive());
+ Self::MIN
+ }
+ }
+ // region: saturating arithmetic
+
+ // region: replacement
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_year(2019),
+ /// Ok(date!(2019 - 02 - 18))
+ /// );
+ /// assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year
+ /// assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+
+ let ordinal = self.ordinal();
+
+ // Dates in January and February are unaffected by leap years.
+ if ordinal <= 59 {
+ return Ok(Self::__from_ordinal_date_unchecked(year, ordinal));
+ }
+
+ match (is_leap_year(self.year()), is_leap_year(year)) {
+ (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)),
+ // February 29 does not exist in common years.
+ (true, false) if ordinal == 60 => Err(error::ComponentRange {
+ name: "day",
+ value: 29,
+ minimum: 1,
+ maximum: 28,
+ conditional_range: true,
+ }),
+ // We're going from a common year to a leap year. Shift dates in March and later by
+ // one day.
+ (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)),
+ // We're going from a leap year to a common year. Shift dates in January and
+ // February by one day.
+ (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)),
+ }
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_month(Month::January),
+ /// Ok(date!(2022 - 01 - 18))
+ /// );
+ /// assert!(
+ /// date!(2022 - 01 - 30)
+ /// .replace_month(Month::February)
+ /// .is_err()
+ /// ); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
+ let (year, _, day) = self.to_calendar_date();
+ Self::from_calendar_date(year, month, day)
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_day(1),
+ /// Ok(date!(2022 - 02 - 01))
+ /// );
+ /// assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day
+ /// assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
+ // Days 1-28 are present in every month, so we can skip checking.
+ if day == 0 || day >= 29 {
+ ensure_value_in_range!(
+ day conditionally in 1 => days_in_year_month(self.year(), self.month())
+ );
+ }
+
+ Ok(Self::__from_ordinal_date_unchecked(
+ self.year(),
+ (self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
+ ))
+ }
+ // endregion replacement
+}
+
+// region: attach time
+/// Methods to add a [`Time`] component, resulting in a [`PrimitiveDateTime`].
+impl Date {
+ /// Create a [`PrimitiveDateTime`] using the existing date. The [`Time`] component will be set
+ /// to midnight.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime};
+ /// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00));
+ /// ```
+ pub const fn midnight(self) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(self, Time::MIDNIGHT)
+ }
+
+ /// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`].
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime, time};
+ /// assert_eq!(
+ /// date!(1970-01-01).with_time(time!(0:00)),
+ /// datetime!(1970-01-01 0:00),
+ /// );
+ /// ```
+ pub const fn with_time(self, time: Time) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(self, time)
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms(hour, minute, second)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_milli(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ millisecond: u16,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_milli(hour, minute, second, millisecond)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_micro(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ microsecond: u32,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_micro(hour, minute, second, microsecond)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_nano(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_nano(hour, minute, second, nanosecond)),
+ ))
+ }
+}
+// endregion attach time
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl Date {
+ /// Format the `Date` using the provided [format description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, error::Format> {
+ format.format_into(output, Some(self), None, None)
+ }
+
+ /// Format the `Date` using the provided [format description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::{format_description};
+ /// # use time_macros::date;
+ /// let format = format_description::parse("[year]-[month]-[day]")?;
+ /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02");
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(Some(self), None, None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl Date {
+ /// Parse a `Date` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::{date, format_description};
+ /// let format = format_description!("[year]-[month]-[day]");
+ /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_date(input.as_bytes())
+ }
+}
+
+impl fmt::Display for Date {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 {
+ write!(
+ f,
+ "{:+}-{:02}-{:02}",
+ self.year(),
+ self.month() as u8,
+ self.day()
+ )
+ } else {
+ write!(
+ f,
+ "{:0width$}-{:02}-{:02}",
+ self.year(),
+ self.month() as u8,
+ self.day(),
+ width = 4 + (self.year() < 0) as usize
+ )
+ }
+ }
+}
+
+impl fmt::Debug for Date {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl Add<Duration> for Date {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ self.checked_add(duration)
+ .expect("overflow adding duration to date")
+ }
+}
+
+impl Add<StdDuration> for Date {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ Self::from_julian_day(self.to_julian_day() + (duration.as_secs() / 86_400) as i32)
+ .expect("overflow adding duration to date")
+ }
+}
+
+impl_add_assign!(Date: Duration, StdDuration);
+
+impl Sub<Duration> for Date {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ self.checked_sub(duration)
+ .expect("overflow subtracting duration from date")
+ }
+}
+
+impl Sub<StdDuration> for Date {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ Self::from_julian_day(self.to_julian_day() - (duration.as_secs() / 86_400) as i32)
+ .expect("overflow subtracting duration from date")
+ }
+}
+
+impl_sub_assign!(Date: Duration, StdDuration);
+
+impl Sub for Date {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Self::Output {
+ Duration::days((self.to_julian_day() - other.to_julian_day()) as _)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/duration.rs b/third_party/rust/time/src/duration.rs
new file mode 100644
index 0000000000..f8d916f451
--- /dev/null
+++ b/third_party/rust/time/src/duration.rs
@@ -0,0 +1,1139 @@
+//! The [`Duration`] struct and its associated `impl`s.
+
+use core::cmp::Ordering;
+use core::fmt;
+use core::iter::Sum;
+use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
+use core::time::Duration as StdDuration;
+
+use crate::error;
+#[cfg(feature = "std")]
+use crate::Instant;
+
+/// By explicitly inserting this enum where padding is expected, the compiler is able to better
+/// perform niche value optimization.
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub(crate) enum Padding {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Optimize,
+}
+
+impl Default for Padding {
+ fn default() -> Self {
+ Self::Optimize
+ }
+}
+
+/// A span of time with nanosecond precision.
+///
+/// Each `Duration` is composed of a whole number of seconds and a fractional part represented in
+/// nanoseconds.
+///
+/// This implementation allows for negative durations, unlike [`core::time::Duration`].
+#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Duration {
+ /// Number of whole seconds.
+ seconds: i64,
+ /// Number of nanoseconds within the second. The sign always matches the `seconds` field.
+ nanoseconds: i32, // always -10^9 < nanoseconds < 10^9
+ #[allow(clippy::missing_docs_in_private_items)]
+ padding: Padding,
+}
+
+impl fmt::Debug for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Duration")
+ .field("seconds", &self.seconds)
+ .field("nanoseconds", &self.nanoseconds)
+ .finish()
+ }
+}
+
+impl Duration {
+ // region: constants
+ /// Equivalent to `0.seconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::ZERO, 0.seconds());
+ /// ```
+ pub const ZERO: Self = Self::seconds(0);
+
+ /// Equivalent to `1.nanoseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::NANOSECOND, 1.nanoseconds());
+ /// ```
+ pub const NANOSECOND: Self = Self::nanoseconds(1);
+
+ /// Equivalent to `1.microseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MICROSECOND, 1.microseconds());
+ /// ```
+ pub const MICROSECOND: Self = Self::microseconds(1);
+
+ /// Equivalent to `1.milliseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MILLISECOND, 1.milliseconds());
+ /// ```
+ pub const MILLISECOND: Self = Self::milliseconds(1);
+
+ /// Equivalent to `1.seconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::SECOND, 1.seconds());
+ /// ```
+ pub const SECOND: Self = Self::seconds(1);
+
+ /// Equivalent to `1.minutes()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MINUTE, 1.minutes());
+ /// ```
+ pub const MINUTE: Self = Self::minutes(1);
+
+ /// Equivalent to `1.hours()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::HOUR, 1.hours());
+ /// ```
+ pub const HOUR: Self = Self::hours(1);
+
+ /// Equivalent to `1.days()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::DAY, 1.days());
+ /// ```
+ pub const DAY: Self = Self::days(1);
+
+ /// Equivalent to `1.weeks()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::WEEK, 1.weeks());
+ /// ```
+ pub const WEEK: Self = Self::weeks(1);
+
+ /// The minimum possible duration. Adding any negative duration to this will cause an overflow.
+ pub const MIN: Self = Self::new_unchecked(i64::MIN, -999_999_999);
+
+ /// The maximum possible duration. Adding any positive duration to this will cause an overflow.
+ pub const MAX: Self = Self::new_unchecked(i64::MAX, 999_999_999);
+ // endregion constants
+
+ // region: is_{sign}
+ /// Check if a duration is exactly zero.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!(0.seconds().is_zero());
+ /// assert!(!1.nanoseconds().is_zero());
+ /// ```
+ pub const fn is_zero(self) -> bool {
+ self.seconds == 0 && self.nanoseconds == 0
+ }
+
+ /// Check if a duration is negative.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!((-1).seconds().is_negative());
+ /// assert!(!0.seconds().is_negative());
+ /// assert!(!1.seconds().is_negative());
+ /// ```
+ pub const fn is_negative(self) -> bool {
+ self.seconds < 0 || self.nanoseconds < 0
+ }
+
+ /// Check if a duration is positive.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!(1.seconds().is_positive());
+ /// assert!(!0.seconds().is_positive());
+ /// assert!(!(-1).seconds().is_positive());
+ /// ```
+ pub const fn is_positive(self) -> bool {
+ self.seconds > 0 || self.nanoseconds > 0
+ }
+ // endregion is_{sign}
+
+ // region: abs
+ /// Get the absolute value of the duration.
+ ///
+ /// This method saturates the returned value if it would otherwise overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().abs(), 1.seconds());
+ /// assert_eq!(0.seconds().abs(), 0.seconds());
+ /// assert_eq!((-1).seconds().abs(), 1.seconds());
+ /// ```
+ pub const fn abs(self) -> Self {
+ Self::new_unchecked(self.seconds.saturating_abs(), self.nanoseconds.abs())
+ }
+
+ /// Convert the existing `Duration` to a `std::time::Duration` and its sign. This returns a
+ /// [`std::time::Duration`] and does not saturate the returned value (unlike [`Duration::abs`]).
+ ///
+ /// ```rust
+ /// # use time::ext::{NumericalDuration, NumericalStdDuration};
+ /// assert_eq!(1.seconds().unsigned_abs(), 1.std_seconds());
+ /// assert_eq!(0.seconds().unsigned_abs(), 0.std_seconds());
+ /// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds());
+ /// ```
+ pub const fn unsigned_abs(self) -> StdDuration {
+ StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs())
+ }
+ // endregion abs
+
+ // region: constructors
+ /// Create a new `Duration` without checking the validity of the components.
+ pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
+ if seconds < 0 {
+ debug_assert!(nanoseconds <= 0);
+ debug_assert!(nanoseconds > -1_000_000_000);
+ } else if seconds > 0 {
+ debug_assert!(nanoseconds >= 0);
+ debug_assert!(nanoseconds < 1_000_000_000);
+ } else {
+ debug_assert!(nanoseconds.unsigned_abs() < 1_000_000_000);
+ }
+
+ Self {
+ seconds,
+ nanoseconds,
+ padding: Padding::Optimize,
+ }
+ }
+
+ /// Create a new `Duration` with the provided seconds and nanoseconds. If nanoseconds is at
+ /// least ±10<sup>9</sup>, it will wrap to the number of seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::new(1, 0), 1.seconds());
+ /// assert_eq!(Duration::new(-1, 0), (-1).seconds());
+ /// assert_eq!(Duration::new(1, 2_000_000_000), 3.seconds());
+ /// ```
+ pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
+ seconds = expect_opt!(
+ seconds.checked_add(nanoseconds as i64 / 1_000_000_000),
+ "overflow constructing `time::Duration`"
+ );
+ nanoseconds %= 1_000_000_000;
+
+ if seconds > 0 && nanoseconds < 0 {
+ // `seconds` cannot overflow here because it is positive.
+ seconds -= 1;
+ nanoseconds += 1_000_000_000;
+ } else if seconds < 0 && nanoseconds > 0 {
+ // `seconds` cannot overflow here because it is negative.
+ seconds += 1;
+ nanoseconds -= 1_000_000_000;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Create a new `Duration` with the given number of weeks. Equivalent to
+ /// `Duration::seconds(weeks * 604_800)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::weeks(1), 604_800.seconds());
+ /// ```
+ pub const fn weeks(weeks: i64) -> Self {
+ Self::seconds(expect_opt!(
+ weeks.checked_mul(604_800),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of days. Equivalent to
+ /// `Duration::seconds(days * 86_400)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::days(1), 86_400.seconds());
+ /// ```
+ pub const fn days(days: i64) -> Self {
+ Self::seconds(expect_opt!(
+ days.checked_mul(86_400),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of hours. Equivalent to
+ /// `Duration::seconds(hours * 3_600)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::hours(1), 3_600.seconds());
+ /// ```
+ pub const fn hours(hours: i64) -> Self {
+ Self::seconds(expect_opt!(
+ hours.checked_mul(3_600),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of minutes. Equivalent to
+ /// `Duration::seconds(minutes * 60)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::minutes(1), 60.seconds());
+ /// ```
+ pub const fn minutes(minutes: i64) -> Self {
+ Self::seconds(expect_opt!(
+ minutes.checked_mul(60),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds(1), 1_000.milliseconds());
+ /// ```
+ pub const fn seconds(seconds: i64) -> Self {
+ Self::new_unchecked(seconds, 0)
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds represented as `f64`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds_f64(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds());
+ /// ```
+ pub fn seconds_f64(seconds: f64) -> Self {
+ if seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
+ crate::expect_failed("overflow constructing `time::Duration`");
+ }
+ if seconds.is_nan() {
+ crate::expect_failed("passed NaN to `time::Duration::seconds_f64`");
+ }
+ Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _)
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds represented as `f32`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds_f32(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds());
+ /// ```
+ pub fn seconds_f32(seconds: f32) -> Self {
+ if seconds > i64::MAX as f32 || seconds < i64::MIN as f32 {
+ crate::expect_failed("overflow constructing `time::Duration`");
+ }
+ if seconds.is_nan() {
+ crate::expect_failed("passed NaN to `time::Duration::seconds_f32`");
+ }
+ Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _)
+ }
+
+ /// Create a new `Duration` with the given number of milliseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::milliseconds(1), 1_000.microseconds());
+ /// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds());
+ /// ```
+ pub const fn milliseconds(milliseconds: i64) -> Self {
+ Self::new_unchecked(
+ milliseconds / 1_000,
+ ((milliseconds % 1_000) * 1_000_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of microseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::microseconds(1), 1_000.nanoseconds());
+ /// assert_eq!(Duration::microseconds(-1), (-1_000).nanoseconds());
+ /// ```
+ pub const fn microseconds(microseconds: i64) -> Self {
+ Self::new_unchecked(
+ microseconds / 1_000_000,
+ ((microseconds % 1_000_000) * 1_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of nanoseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::nanoseconds(1), 1.microseconds() / 1_000);
+ /// assert_eq!(Duration::nanoseconds(-1), (-1).microseconds() / 1_000);
+ /// ```
+ pub const fn nanoseconds(nanoseconds: i64) -> Self {
+ Self::new_unchecked(
+ nanoseconds / 1_000_000_000,
+ (nanoseconds % 1_000_000_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of nanoseconds.
+ ///
+ /// As the input range cannot be fully mapped to the output, this should only be used where it's
+ /// known to result in a valid value.
+ pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self {
+ let seconds = nanoseconds / 1_000_000_000;
+ let nanoseconds = nanoseconds % 1_000_000_000;
+
+ if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 {
+ crate::expect_failed("overflow constructing `time::Duration`");
+ }
+
+ Self::new_unchecked(seconds as _, nanoseconds as _)
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the number of whole weeks in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.weeks().whole_weeks(), 1);
+ /// assert_eq!((-1).weeks().whole_weeks(), -1);
+ /// assert_eq!(6.days().whole_weeks(), 0);
+ /// assert_eq!((-6).days().whole_weeks(), 0);
+ /// ```
+ pub const fn whole_weeks(self) -> i64 {
+ self.whole_seconds() / 604_800
+ }
+
+ /// Get the number of whole days in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.days().whole_days(), 1);
+ /// assert_eq!((-1).days().whole_days(), -1);
+ /// assert_eq!(23.hours().whole_days(), 0);
+ /// assert_eq!((-23).hours().whole_days(), 0);
+ /// ```
+ pub const fn whole_days(self) -> i64 {
+ self.whole_seconds() / 86_400
+ }
+
+ /// Get the number of whole hours in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.hours().whole_hours(), 1);
+ /// assert_eq!((-1).hours().whole_hours(), -1);
+ /// assert_eq!(59.minutes().whole_hours(), 0);
+ /// assert_eq!((-59).minutes().whole_hours(), 0);
+ /// ```
+ pub const fn whole_hours(self) -> i64 {
+ self.whole_seconds() / 3_600
+ }
+
+ /// Get the number of whole minutes in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.minutes().whole_minutes(), 1);
+ /// assert_eq!((-1).minutes().whole_minutes(), -1);
+ /// assert_eq!(59.seconds().whole_minutes(), 0);
+ /// assert_eq!((-59).seconds().whole_minutes(), 0);
+ /// ```
+ pub const fn whole_minutes(self) -> i64 {
+ self.whole_seconds() / 60
+ }
+
+ /// Get the number of whole seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().whole_seconds(), 1);
+ /// assert_eq!((-1).seconds().whole_seconds(), -1);
+ /// assert_eq!(1.minutes().whole_seconds(), 60);
+ /// assert_eq!((-1).minutes().whole_seconds(), -60);
+ /// ```
+ pub const fn whole_seconds(self) -> i64 {
+ self.seconds
+ }
+
+ /// Get the number of fractional seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.5.seconds().as_seconds_f64(), 1.5);
+ /// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5);
+ /// ```
+ pub fn as_seconds_f64(self) -> f64 {
+ self.seconds as f64 + self.nanoseconds as f64 / 1_000_000_000.
+ }
+
+ /// Get the number of fractional seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.5.seconds().as_seconds_f32(), 1.5);
+ /// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5);
+ /// ```
+ pub fn as_seconds_f32(self) -> f32 {
+ self.seconds as f32 + self.nanoseconds as f32 / 1_000_000_000.
+ }
+
+ /// Get the number of whole milliseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().whole_milliseconds(), 1_000);
+ /// assert_eq!((-1).seconds().whole_milliseconds(), -1_000);
+ /// assert_eq!(1.milliseconds().whole_milliseconds(), 1);
+ /// assert_eq!((-1).milliseconds().whole_milliseconds(), -1);
+ /// ```
+ pub const fn whole_milliseconds(self) -> i128 {
+ self.seconds as i128 * 1_000 + self.nanoseconds as i128 / 1_000_000
+ }
+
+ /// Get the number of milliseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000..1_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.4.seconds().subsec_milliseconds(), 400);
+ /// assert_eq!((-1.4).seconds().subsec_milliseconds(), -400);
+ /// ```
+ // Allow the lint, as the value is guaranteed to be less than 1000.
+ pub const fn subsec_milliseconds(self) -> i16 {
+ (self.nanoseconds / 1_000_000) as _
+ }
+
+ /// Get the number of whole microseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.milliseconds().whole_microseconds(), 1_000);
+ /// assert_eq!((-1).milliseconds().whole_microseconds(), -1_000);
+ /// assert_eq!(1.microseconds().whole_microseconds(), 1);
+ /// assert_eq!((-1).microseconds().whole_microseconds(), -1);
+ /// ```
+ pub const fn whole_microseconds(self) -> i128 {
+ self.seconds as i128 * 1_000_000 + self.nanoseconds as i128 / 1_000
+ }
+
+ /// Get the number of microseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000_000..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.0004.seconds().subsec_microseconds(), 400);
+ /// assert_eq!((-1.0004).seconds().subsec_microseconds(), -400);
+ /// ```
+ pub const fn subsec_microseconds(self) -> i32 {
+ self.nanoseconds / 1_000
+ }
+
+ /// Get the number of nanoseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.microseconds().whole_nanoseconds(), 1_000);
+ /// assert_eq!((-1).microseconds().whole_nanoseconds(), -1_000);
+ /// assert_eq!(1.nanoseconds().whole_nanoseconds(), 1);
+ /// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1);
+ /// ```
+ pub const fn whole_nanoseconds(self) -> i128 {
+ self.seconds as i128 * 1_000_000_000 + self.nanoseconds as i128
+ }
+
+ /// Get the number of nanoseconds past the number of whole seconds.
+ ///
+ /// The returned value will always be in the range `-1_000_000_000..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.000_000_400.seconds().subsec_nanoseconds(), 400);
+ /// assert_eq!((-1.000_000_400).seconds().subsec_nanoseconds(), -400);
+ /// ```
+ pub const fn subsec_nanoseconds(self) -> i32 {
+ self.nanoseconds
+ }
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_add(5.seconds()), Some(10.seconds()));
+ /// assert_eq!(Duration::MAX.checked_add(1.nanoseconds()), None);
+ /// assert_eq!((-5).seconds().checked_add(5.seconds()), Some(0.seconds()));
+ /// ```
+ pub const fn checked_add(self, rhs: Self) -> Option<Self> {
+ let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds));
+ let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_sub(1));
+ }
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self - rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_sub(5.seconds()), Some(Duration::ZERO));
+ /// assert_eq!(Duration::MIN.checked_sub(1.nanoseconds()), None);
+ /// assert_eq!(5.seconds().checked_sub(10.seconds()), Some((-5).seconds()));
+ /// ```
+ pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
+ let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds));
+ let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_sub(1));
+ }
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self * rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_mul(2), Some(10.seconds()));
+ /// assert_eq!(5.seconds().checked_mul(-2), Some((-10).seconds()));
+ /// assert_eq!(5.seconds().checked_mul(0), Some(0.seconds()));
+ /// assert_eq!(Duration::MAX.checked_mul(2), None);
+ /// assert_eq!(Duration::MIN.checked_mul(2), None);
+ /// ```
+ pub const fn checked_mul(self, rhs: i32) -> Option<Self> {
+ // Multiply nanoseconds as i64, because it cannot overflow that way.
+ let total_nanos = self.nanoseconds as i64 * rhs as i64;
+ let extra_secs = total_nanos / 1_000_000_000;
+ let nanoseconds = (total_nanos % 1_000_000_000) as _;
+ let seconds = const_try_opt!(
+ const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs)
+ );
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(10.seconds().checked_div(2), Some(5.seconds()));
+ /// assert_eq!(10.seconds().checked_div(-2), Some((-5).seconds()));
+ /// assert_eq!(1.seconds().checked_div(0), None);
+ /// ```
+ pub const fn checked_div(self, rhs: i32) -> Option<Self> {
+ let seconds = const_try_opt!(self.seconds.checked_div(rhs as i64));
+ let carry = self.seconds - seconds * (rhs as i64);
+ let extra_nanos = const_try_opt!((carry * 1_000_000_000).checked_div(rhs as i64));
+ let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32);
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+ // endregion checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_add(5.seconds()), 10.seconds());
+ /// assert_eq!(Duration::MAX.saturating_add(1.nanoseconds()), Duration::MAX);
+ /// assert_eq!(
+ /// Duration::MIN.saturating_add((-1).nanoseconds()),
+ /// Duration::MIN
+ /// );
+ /// assert_eq!((-5).seconds().saturating_add(5.seconds()), Duration::ZERO);
+ /// ```
+ pub const fn saturating_add(self, rhs: Self) -> Self {
+ let (mut seconds, overflow) = self.seconds.overflowing_add(rhs.seconds);
+ if overflow {
+ if self.seconds > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = match seconds.checked_sub(1) {
+ Some(seconds) => seconds,
+ None => return Self::MIN,
+ };
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Computes `self - rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_sub(5.seconds()), Duration::ZERO);
+ /// assert_eq!(Duration::MIN.saturating_sub(1.nanoseconds()), Duration::MIN);
+ /// assert_eq!(
+ /// Duration::MAX.saturating_sub((-1).nanoseconds()),
+ /// Duration::MAX
+ /// );
+ /// assert_eq!(5.seconds().saturating_sub(10.seconds()), (-5).seconds());
+ /// ```
+ pub const fn saturating_sub(self, rhs: Self) -> Self {
+ let (mut seconds, overflow) = self.seconds.overflowing_sub(rhs.seconds);
+ if overflow {
+ if self.seconds > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = match seconds.checked_sub(1) {
+ Some(seconds) => seconds,
+ None => return Self::MIN,
+ };
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Computes `self * rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_mul(2), 10.seconds());
+ /// assert_eq!(5.seconds().saturating_mul(-2), (-10).seconds());
+ /// assert_eq!(5.seconds().saturating_mul(0), Duration::ZERO);
+ /// assert_eq!(Duration::MAX.saturating_mul(2), Duration::MAX);
+ /// assert_eq!(Duration::MIN.saturating_mul(2), Duration::MIN);
+ /// assert_eq!(Duration::MAX.saturating_mul(-2), Duration::MIN);
+ /// assert_eq!(Duration::MIN.saturating_mul(-2), Duration::MAX);
+ /// ```
+ pub const fn saturating_mul(self, rhs: i32) -> Self {
+ // Multiply nanoseconds as i64, because it cannot overflow that way.
+ let total_nanos = self.nanoseconds as i64 * rhs as i64;
+ let extra_secs = total_nanos / 1_000_000_000;
+ let nanoseconds = (total_nanos % 1_000_000_000) as _;
+ let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _);
+ if overflow1 {
+ if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let (seconds, overflow2) = seconds.overflowing_add(extra_secs);
+ if overflow2 {
+ if self.seconds > 0 && rhs > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+ // endregion saturating arithmetic
+
+ /// Runs a closure, returning the duration of time it took to run. The return value of the
+ /// closure is provided in the second part of the tuple.
+ #[cfg(feature = "std")]
+ pub fn time_fn<T>(f: impl FnOnce() -> T) -> (Self, T) {
+ let start = Instant::now();
+ let return_value = f();
+ let end = Instant::now();
+
+ (end - start, return_value)
+ }
+}
+
+// region: trait impls
+/// The format returned by this implementation is not stable and must not be relied upon.
+///
+/// By default this produces an exact, full-precision printout of the duration.
+/// For a concise, rounded printout instead, you can use the `.N` format specifier:
+///
+/// ```
+/// # use time::Duration;
+/// #
+/// let duration = Duration::new(123456, 789011223);
+/// println!("{duration:.3}");
+/// ```
+///
+/// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60
+/// seconds.
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.is_negative() {
+ f.write_str("-")?;
+ }
+
+ if let Some(_precision) = f.precision() {
+ // Concise, rounded representation.
+
+ if self.is_zero() {
+ // Write a zero value with the requested precision.
+ return (0.).fmt(f).and_then(|_| f.write_str("s"));
+ }
+
+ /// Format the first item that produces a value greater than 1 and then break.
+ macro_rules! item {
+ ($name:literal, $value:expr) => {
+ let value = $value;
+ if value >= 1.0 {
+ return value.fmt(f).and_then(|_| f.write_str($name));
+ }
+ };
+ }
+
+ // Even if this produces a de-normal float, because we're rounding we don't really care.
+ let seconds = self.unsigned_abs().as_secs_f64();
+
+ item!("d", seconds / 86_400.);
+ item!("h", seconds / 3_600.);
+ item!("m", seconds / 60.);
+ item!("s", seconds);
+ item!("ms", seconds * 1_000.);
+ item!("µs", seconds * 1_000_000.);
+ item!("ns", seconds * 1_000_000_000.);
+ } else {
+ // Precise, but verbose representation.
+
+ if self.is_zero() {
+ return f.write_str("0s");
+ }
+
+ /// Format a single item.
+ macro_rules! item {
+ ($name:literal, $value:expr) => {
+ match $value {
+ 0 => Ok(()),
+ value => value.fmt(f).and_then(|_| f.write_str($name)),
+ }
+ };
+ }
+
+ let seconds = self.seconds.unsigned_abs();
+ let nanoseconds = self.nanoseconds.unsigned_abs();
+
+ item!("d", seconds / 86_400)?;
+ item!("h", seconds / 3_600 % 24)?;
+ item!("m", seconds / 60 % 60)?;
+ item!("s", seconds % 60)?;
+ item!("ms", nanoseconds / 1_000_000)?;
+ item!("µs", nanoseconds / 1_000 % 1_000)?;
+ item!("ns", nanoseconds % 1_000)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl TryFrom<StdDuration> for Duration {
+ type Error = error::ConversionRange;
+
+ fn try_from(original: StdDuration) -> Result<Self, error::ConversionRange> {
+ Ok(Self::new(
+ original
+ .as_secs()
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ original.subsec_nanos() as _,
+ ))
+ }
+}
+
+impl TryFrom<Duration> for StdDuration {
+ type Error = error::ConversionRange;
+
+ fn try_from(duration: Duration) -> Result<Self, error::ConversionRange> {
+ Ok(Self::new(
+ duration
+ .seconds
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ duration
+ .nanoseconds
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ ))
+ }
+}
+
+impl Add for Duration {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ self.checked_add(rhs)
+ .expect("overflow when adding durations")
+ }
+}
+
+impl Add<StdDuration> for Duration {
+ type Output = Self;
+
+ fn add(self, std_duration: StdDuration) -> Self::Output {
+ self + Self::try_from(std_duration)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ }
+}
+
+impl Add<Duration> for StdDuration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ rhs + self
+ }
+}
+
+impl_add_assign!(Duration: Self, StdDuration);
+
+impl AddAssign<Duration> for StdDuration {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = (*self + rhs).try_into().expect(
+ "Cannot represent a resulting duration in std. Try `let x = x + rhs;`, which will \
+ change the type.",
+ );
+ }
+}
+
+impl Neg for Duration {
+ type Output = Self;
+
+ fn neg(self) -> Self::Output {
+ Self::new_unchecked(-self.seconds, -self.nanoseconds)
+ }
+}
+
+impl Sub for Duration {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ self.checked_sub(rhs)
+ .expect("overflow when subtracting durations")
+ }
+}
+
+impl Sub<StdDuration> for Duration {
+ type Output = Self;
+
+ fn sub(self, rhs: StdDuration) -> Self::Output {
+ self - Self::try_from(rhs)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ }
+}
+
+impl Sub<Duration> for StdDuration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ Duration::try_from(self)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ - rhs
+ }
+}
+
+impl_sub_assign!(Duration: Self, StdDuration);
+
+impl SubAssign<Duration> for StdDuration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = (*self - rhs).try_into().expect(
+ "Cannot represent a resulting duration in std. Try `let x = x - rhs;`, which will \
+ change the type.",
+ );
+ }
+}
+
+/// Implement `Mul` (reflexively) and `Div` for `Duration` for various types.
+macro_rules! duration_mul_div_int {
+ ($($type:ty),+) => {$(
+ impl Mul<$type> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: $type) -> Self::Output {
+ Self::nanoseconds_i128(
+ self.whole_nanoseconds()
+ .checked_mul(rhs as _)
+ .expect("overflow when multiplying duration")
+ )
+ }
+ }
+
+ impl Mul<Duration> for $type {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+ }
+
+ impl Div<$type> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: $type) -> Self::Output {
+ Self::nanoseconds_i128(self.whole_nanoseconds() / rhs as i128)
+ }
+ }
+ )+};
+}
+duration_mul_div_int![i8, i16, i32, u8, u16, u32];
+
+impl Mul<f32> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: f32) -> Self::Output {
+ Self::seconds_f32(self.as_seconds_f32() * rhs)
+ }
+}
+
+impl Mul<Duration> for f32 {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+}
+
+impl Mul<f64> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: f64) -> Self::Output {
+ Self::seconds_f64(self.as_seconds_f64() * rhs)
+ }
+}
+
+impl Mul<Duration> for f64 {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+}
+
+impl_mul_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64);
+
+impl Div<f32> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: f32) -> Self::Output {
+ Self::seconds_f32(self.as_seconds_f32() / rhs)
+ }
+}
+
+impl Div<f64> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: f64) -> Self::Output {
+ Self::seconds_f64(self.as_seconds_f64() / rhs)
+ }
+}
+
+impl_div_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64);
+
+impl Div for Duration {
+ type Output = f64;
+
+ fn div(self, rhs: Self) -> Self::Output {
+ self.as_seconds_f64() / rhs.as_seconds_f64()
+ }
+}
+
+impl Div<StdDuration> for Duration {
+ type Output = f64;
+
+ fn div(self, rhs: StdDuration) -> Self::Output {
+ self.as_seconds_f64() / rhs.as_secs_f64()
+ }
+}
+
+impl Div<Duration> for StdDuration {
+ type Output = f64;
+
+ fn div(self, rhs: Duration) -> Self::Output {
+ self.as_secs_f64() / rhs.as_seconds_f64()
+ }
+}
+
+impl PartialEq<StdDuration> for Duration {
+ fn eq(&self, rhs: &StdDuration) -> bool {
+ Ok(*self) == Self::try_from(*rhs)
+ }
+}
+
+impl PartialEq<Duration> for StdDuration {
+ fn eq(&self, rhs: &Duration) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialOrd<StdDuration> for Duration {
+ fn partial_cmp(&self, rhs: &StdDuration) -> Option<Ordering> {
+ if rhs.as_secs() > i64::MAX as _ {
+ return Some(Ordering::Less);
+ }
+
+ Some(
+ self.seconds
+ .cmp(&(rhs.as_secs() as _))
+ .then_with(|| self.nanoseconds.cmp(&(rhs.subsec_nanos() as _))),
+ )
+ }
+}
+
+impl PartialOrd<Duration> for StdDuration {
+ fn partial_cmp(&self, rhs: &Duration) -> Option<Ordering> {
+ rhs.partial_cmp(self).map(Ordering::reverse)
+ }
+}
+
+impl Sum for Duration {
+ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+ iter.reduce(|a, b| a + b).unwrap_or_default()
+ }
+}
+
+impl<'a> Sum<&'a Self> for Duration {
+ fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
+ iter.copied().sum()
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/error/component_range.rs b/third_party/rust/time/src/error/component_range.rs
new file mode 100644
index 0000000000..f399356f4d
--- /dev/null
+++ b/third_party/rust/time/src/error/component_range.rs
@@ -0,0 +1,92 @@
+//! 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,
+ /// Minimum allowed value, inclusive.
+ pub(crate) minimum: i64,
+ /// Maximum allowed value, inclusive.
+ pub(crate) maximum: i64,
+ /// Value that was provided.
+ pub(crate) value: i64,
+ /// The minimum and/or maximum value is conditional on the value of other
+ /// parameters.
+ pub(crate) conditional_range: bool,
+}
+
+impl ComponentRange {
+ /// Obtain the name of the component whose value was out of range.
+ 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.
+ pub const fn is_conditional(self) -> bool {
+ self.conditional_range
+ }
+}
+
+impl fmt::Display for ComponentRange {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{} must be in the range {}..={}",
+ self.name, self.minimum, self.maximum
+ )?;
+
+ if self.conditional_range {
+ f.write_str(", given values of other parameters")?;
+ }
+
+ Ok(())
+ }
+}
+
+impl From<ComponentRange> for crate::Error {
+ fn from(original: ComponentRange) -> Self {
+ Self::ComponentRange(original)
+ }
+}
+
+impl TryFrom<crate::Error> for ComponentRange {
+ type Error = error::DifferentVariant;
+
+ 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::de::Expected for ComponentRange {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "a value in the range {}..={}",
+ self.minimum, self.maximum
+ )
+ }
+}
+
+#[cfg(feature = "serde")]
+impl ComponentRange {
+ /// Convert the error to a deserialization error.
+ pub(crate) fn into_de_error<E: serde::de::Error>(self) -> E {
+ E::invalid_value(serde::de::Unexpected::Signed(self.value), &self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ComponentRange {}
diff --git a/third_party/rust/time/src/error/conversion_range.rs b/third_party/rust/time/src/error/conversion_range.rs
new file mode 100644
index 0000000000..d6d9243e13
--- /dev/null
+++ b/third_party/rust/time/src/error/conversion_range.rs
@@ -0,0 +1,36 @@
+//! 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 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Source value is out of range for the target type")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ConversionRange {}
+
+impl From<ConversionRange> for crate::Error {
+ fn from(err: ConversionRange) -> Self {
+ Self::ConversionRange(err)
+ }
+}
+
+impl TryFrom<crate::Error> for ConversionRange {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ConversionRange(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/different_variant.rs b/third_party/rust/time/src/error/different_variant.rs
new file mode 100644
index 0000000000..22e21cb0c0
--- /dev/null
+++ b/third_party/rust/time/src/error/different_variant.rs
@@ -0,0 +1,34 @@
+//! 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 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "value was of a different variant than required")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for DifferentVariant {}
+
+impl From<DifferentVariant> for crate::Error {
+ fn from(err: DifferentVariant) -> Self {
+ Self::DifferentVariant(err)
+ }
+}
+
+impl TryFrom<crate::Error> for DifferentVariant {
+ type Error = Self;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::DifferentVariant(err) => Ok(err),
+ _ => Err(Self),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/format.rs b/third_party/rust/time/src/error/format.rs
new file mode 100644
index 0000000000..94d134363d
--- /dev/null
+++ b/third_party/rust/time/src/error/format.rs
@@ -0,0 +1,92 @@
+//! Error formatting a struct
+
+use core::fmt;
+use std::io;
+
+use crate::error;
+
+/// An error occurred when formatting.
+#[non_exhaustive]
+#[allow(missing_copy_implementations)]
+#[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 value of `std::io::Error` was returned internally.
+ StdIo(io::Error),
+}
+
+impl fmt::Display for Format {
+ 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::StdIo(err) => err.fmt(f),
+ }
+ }
+}
+
+impl From<io::Error> for Format {
+ fn from(err: io::Error) -> Self {
+ Self::StdIo(err)
+ }
+}
+
+impl TryFrom<Format> for io::Error {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: Format) -> Result<Self, Self::Error> {
+ match err {
+ Format::StdIo(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Format {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match *self {
+ Self::InsufficientTypeInformation | Self::InvalidComponent(_) => None,
+ Self::StdIo(ref err) => Some(err),
+ }
+ }
+}
+
+impl From<Format> for crate::Error {
+ fn from(original: Format) -> Self {
+ Self::Format(original)
+ }
+}
+
+impl TryFrom<crate::Error> for Format {
+ type Error = error::DifferentVariant;
+
+ 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
+ pub fn into_invalid_serde_value<S: serde::Serializer>(self) -> S::Error {
+ use serde::ser::Error;
+ S::Error::custom(self)
+ }
+}
diff --git a/third_party/rust/time/src/error/indeterminate_offset.rs b/third_party/rust/time/src/error/indeterminate_offset.rs
new file mode 100644
index 0000000000..d25d4164ec
--- /dev/null
+++ b/third_party/rust/time/src/error/indeterminate_offset.rs
@@ -0,0 +1,35 @@
+//! 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 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("The system's UTC offset could not be determined")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for IndeterminateOffset {}
+
+impl From<IndeterminateOffset> for crate::Error {
+ fn from(err: IndeterminateOffset) -> Self {
+ Self::IndeterminateOffset(err)
+ }
+}
+
+impl TryFrom<crate::Error> for IndeterminateOffset {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::IndeterminateOffset(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/invalid_format_description.rs b/third_party/rust/time/src/error/invalid_format_description.rs
new file mode 100644
index 0000000000..29c46edb16
--- /dev/null
+++ b/third_party/rust/time/src/error/invalid_format_description.rs
@@ -0,0 +1,80 @@
+//! 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,
+ },
+}
+
+impl From<InvalidFormatDescription> for crate::Error {
+ fn from(original: InvalidFormatDescription) -> Self {
+ Self::InvalidFormatDescription(original)
+ }
+}
+
+impl TryFrom<crate::Error> for InvalidFormatDescription {
+ type Error = error::DifferentVariant;
+
+ 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 {
+ 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}")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidFormatDescription {}
diff --git a/third_party/rust/time/src/error/invalid_variant.rs b/third_party/rust/time/src/error/invalid_variant.rs
new file mode 100644
index 0000000000..396a749a29
--- /dev/null
+++ b/third_party/rust/time/src/error/invalid_variant.rs
@@ -0,0 +1,34 @@
+//! 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 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "value was not a valid variant")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidVariant {}
+
+impl From<InvalidVariant> for crate::Error {
+ fn from(err: InvalidVariant) -> Self {
+ Self::InvalidVariant(err)
+ }
+}
+
+impl TryFrom<crate::Error> for InvalidVariant {
+ type Error = crate::error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::InvalidVariant(err) => Ok(err),
+ _ => Err(crate::error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/mod.rs b/third_party/rust/time/src/error/mod.rs
new file mode 100644
index 0000000000..346b89f748
--- /dev/null
+++ b/third_party/rust/time/src/error/mod.rs
@@ -0,0 +1,112 @@
+//! 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;
+
+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.
+#[allow(missing_copy_implementations, variant_size_differences)]
+#[allow(clippy::missing_docs_in_private_items)] // variants only
+#[non_exhaustive]
+#[derive(Debug)]
+pub enum Error {
+ ConversionRange(ConversionRange),
+ ComponentRange(ComponentRange),
+ #[cfg(feature = "local-offset")]
+ IndeterminateOffset(IndeterminateOffset),
+ #[cfg(feature = "formatting")]
+ Format(Format),
+ #[cfg(feature = "parsing")]
+ ParseFromDescription(ParseFromDescription),
+ #[cfg(feature = "parsing")]
+ #[non_exhaustive]
+ UnexpectedTrailingCharacters,
+ #[cfg(feature = "parsing")]
+ TryFromParsed(TryFromParsed),
+ #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+ InvalidFormatDescription(InvalidFormatDescription),
+ DifferentVariant(DifferentVariant),
+ InvalidVariant(InvalidVariant),
+}
+
+impl fmt::Display for Error {
+ 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")]
+ Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
+ #[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),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::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")]
+ Self::UnexpectedTrailingCharacters => None,
+ #[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),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/parse.rs b/third_party/rust/time/src/error/parse.rs
new file mode 100644
index 0000000000..b90ac74e73
--- /dev/null
+++ b/third_party/rust/time/src/error/parse.rs
@@ -0,0 +1,97 @@
+//! Error that occurred at some stage of parsing
+
+use core::fmt;
+
+use crate::error::{self, ParseFromDescription, TryFromParsed};
+
+/// An error that occurred at some stage of parsing.
+#[allow(variant_size_differences)]
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Parse {
+ #[allow(clippy::missing_docs_in_private_items)]
+ TryFromParsed(TryFromParsed),
+ #[allow(clippy::missing_docs_in_private_items)]
+ ParseFromDescription(ParseFromDescription),
+ /// The input should have ended, but there were characters remaining.
+ #[non_exhaustive]
+ UnexpectedTrailingCharacters,
+}
+
+impl fmt::Display for Parse {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::TryFromParsed(err) => err.fmt(f),
+ Self::ParseFromDescription(err) => err.fmt(f),
+ Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Parse {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::TryFromParsed(err) => Some(err),
+ Self::ParseFromDescription(err) => Some(err),
+ Self::UnexpectedTrailingCharacters => None,
+ }
+ }
+}
+
+impl From<TryFromParsed> for Parse {
+ fn from(err: TryFromParsed) -> Self {
+ Self::TryFromParsed(err)
+ }
+}
+
+impl TryFrom<Parse> for TryFromParsed {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: Parse) -> Result<Self, Self::Error> {
+ match err {
+ Parse::TryFromParsed(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl From<ParseFromDescription> for Parse {
+ fn from(err: ParseFromDescription) -> Self {
+ Self::ParseFromDescription(err)
+ }
+}
+
+impl TryFrom<Parse> for ParseFromDescription {
+ type Error = error::DifferentVariant;
+
+ 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 {
+ fn from(err: Parse) -> Self {
+ match err {
+ Parse::TryFromParsed(err) => Self::TryFromParsed(err),
+ Parse::ParseFromDescription(err) => Self::ParseFromDescription(err),
+ Parse::UnexpectedTrailingCharacters => Self::UnexpectedTrailingCharacters,
+ }
+ }
+}
+
+impl TryFrom<crate::Error> for Parse {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)),
+ crate::Error::UnexpectedTrailingCharacters => Ok(Self::UnexpectedTrailingCharacters),
+ crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/parse_from_description.rs b/third_party/rust/time/src/error/parse_from_description.rs
new file mode 100644
index 0000000000..772f10d173
--- /dev/null
+++ b/third_party/rust/time/src/error/parse_from_description.rs
@@ -0,0 +1,47 @@
+//! 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),
+}
+
+impl fmt::Display for ParseFromDescription {
+ 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")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseFromDescription {}
+
+impl From<ParseFromDescription> for crate::Error {
+ fn from(original: ParseFromDescription) -> Self {
+ Self::ParseFromDescription(original)
+ }
+}
+
+impl TryFrom<crate::Error> for ParseFromDescription {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ParseFromDescription(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/try_from_parsed.rs b/third_party/rust/time/src/error/try_from_parsed.rs
new file mode 100644
index 0000000000..da8e6947a3
--- /dev/null
+++ b/third_party/rust/time/src/error/try_from_parsed.rs
@@ -0,0 +1,71 @@
+//! 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]
+#[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 {
+ 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 {
+ fn from(v: error::ComponentRange) -> Self {
+ Self::ComponentRange(v)
+ }
+}
+
+impl TryFrom<TryFromParsed> for error::ComponentRange {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: TryFromParsed) -> Result<Self, Self::Error> {
+ match err {
+ TryFromParsed::ComponentRange(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for TryFromParsed {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::InsufficientInformation => None,
+ Self::ComponentRange(err) => Some(err),
+ }
+ }
+}
+
+impl From<TryFromParsed> for crate::Error {
+ fn from(original: TryFromParsed) -> Self {
+ Self::TryFromParsed(original)
+ }
+}
+
+impl TryFrom<crate::Error> for TryFromParsed {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::TryFromParsed(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/ext.rs b/third_party/rust/time/src/ext.rs
new file mode 100644
index 0000000000..5a1393d86f
--- /dev/null
+++ b/third_party/rust/time/src/ext.rs
@@ -0,0 +1,279 @@
+//! Extension traits.
+
+use core::time::Duration as StdDuration;
+
+use crate::Duration;
+
+/// Sealed trait to prevent downstream implementations.
+mod sealed {
+ /// A trait that cannot be implemented by downstream users.
+ pub trait Sealed {}
+ impl Sealed for i64 {}
+ impl Sealed for u64 {}
+ impl Sealed for f64 {}
+}
+
+// region: NumericalDuration
+/// Create [`Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`Duration`]s.
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5));
+/// assert_eq!(5.microseconds(), Duration::microseconds(5));
+/// assert_eq!(5.milliseconds(), Duration::milliseconds(5));
+/// assert_eq!(5.seconds(), Duration::seconds(5));
+/// assert_eq!(5.minutes(), Duration::minutes(5));
+/// assert_eq!(5.hours(), Duration::hours(5));
+/// assert_eq!(5.days(), Duration::days(5));
+/// assert_eq!(5.weeks(), Duration::weeks(5));
+/// ```
+///
+/// Signed integers work as well!
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5));
+/// assert_eq!((-5).microseconds(), Duration::microseconds(-5));
+/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5));
+/// assert_eq!((-5).seconds(), Duration::seconds(-5));
+/// assert_eq!((-5).minutes(), Duration::minutes(-5));
+/// assert_eq!((-5).hours(), Duration::hours(-5));
+/// assert_eq!((-5).days(), Duration::days(-5));
+/// assert_eq!((-5).weeks(), Duration::weeks(-5));
+/// ```
+///
+/// Just like any other [`Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalDuration;
+/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds());
+/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds());
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalDuration: sealed::Sealed {
+ /// Create a [`Duration`] from the number of nanoseconds.
+ fn nanoseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of microseconds.
+ fn microseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of milliseconds.
+ fn milliseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of seconds.
+ fn seconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of minutes.
+ fn minutes(self) -> Duration;
+ /// Create a [`Duration`] from the number of hours.
+ fn hours(self) -> Duration;
+ /// Create a [`Duration`] from the number of days.
+ fn days(self) -> Duration;
+ /// Create a [`Duration`] from the number of weeks.
+ fn weeks(self) -> Duration;
+}
+
+impl NumericalDuration for i64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::microseconds(self)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::milliseconds(self)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::seconds(self)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::minutes(self)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::hours(self)
+ }
+
+ fn days(self) -> Duration {
+ Duration::days(self)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::weeks(self)
+ }
+}
+
+impl NumericalDuration for f64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self as _)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000.) as _)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000_000.) as _)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000_000_000.) as _)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::nanoseconds((self * 60_000_000_000.) as _)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::nanoseconds((self * 3_600_000_000_000.) as _)
+ }
+
+ fn days(self) -> Duration {
+ Duration::nanoseconds((self * 86_400_000_000_000.) as _)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::nanoseconds((self * 604_800_000_000_000.) as _)
+ }
+}
+// endregion NumericalDuration
+
+// region: NumericalStdDuration
+/// Create [`std::time::Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`std::time::Duration`]s.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// # use core::time::Duration;
+/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5));
+/// assert_eq!(5.std_microseconds(), Duration::from_micros(5));
+/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5));
+/// assert_eq!(5.std_seconds(), Duration::from_secs(5));
+/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60));
+/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600));
+/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400));
+/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800));
+/// ```
+///
+/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// assert_eq!(
+/// 2.std_seconds() + 500.std_milliseconds(),
+/// 2_500.std_milliseconds()
+/// );
+/// assert_eq!(
+/// 2.std_seconds() - 500.std_milliseconds(),
+/// 1_500.std_milliseconds()
+/// );
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalStdDuration: sealed::Sealed {
+ /// Create a [`std::time::Duration`] from the number of nanoseconds.
+ fn std_nanoseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of microseconds.
+ fn std_microseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of milliseconds.
+ fn std_milliseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of seconds.
+ fn std_seconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of minutes.
+ fn std_minutes(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of hours.
+ fn std_hours(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of days.
+ fn std_days(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of weeks.
+ fn std_weeks(self) -> StdDuration;
+}
+
+impl NumericalStdDuration for u64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ StdDuration::from_nanos(self)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ StdDuration::from_micros(self)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ StdDuration::from_millis(self)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ StdDuration::from_secs(self)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ StdDuration::from_secs(self * 60)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ StdDuration::from_secs(self * 3_600)
+ }
+
+ fn std_days(self) -> StdDuration {
+ StdDuration::from_secs(self * 86_400)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ StdDuration::from_secs(self * 604_800)
+ }
+}
+
+impl NumericalStdDuration for f64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos(self as _)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000.) as _)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000_000.) as _)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000_000_000.) as _)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 60_000_000_000.) as _)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 3_600_000_000_000.) as _)
+ }
+
+ fn std_days(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 86_400_000_000_000.) as _)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 604_800_000_000_000.) as _)
+ }
+}
+// endregion NumericalStdDuration
diff --git a/third_party/rust/time/src/format_description/borrowed_format_item.rs b/third_party/rust/time/src/format_description/borrowed_format_item.rs
new file mode 100644
index 0000000000..425b902878
--- /dev/null
+++ b/third_party/rust/time/src/format_description/borrowed_format_item.rs
@@ -0,0 +1,106 @@
+//! 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, PartialEq, Eq)]
+pub enum BorrowedFormatItem<'a> {
+ /// Bytes that are formatted as-is.
+ ///
+ /// **Note**: If you call the `format` method that returns a `String`, these bytes will be
+ /// passed through `String::from_utf8_lossy`.
+ Literal(&'a [u8]),
+ /// 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<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(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 From<Component> for BorrowedFormatItem<'_> {
+ fn from(component: Component) -> Self {
+ Self::Component(component)
+ }
+}
+
+impl TryFrom<BorrowedFormatItem<'_>> for Component {
+ type Error = error::DifferentVariant;
+
+ 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> {
+ fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self {
+ Self::Compound(items)
+ }
+}
+
+impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] {
+ type Error = error::DifferentVariant;
+
+ 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<'_> {
+ fn eq(&self, rhs: &Component) -> bool {
+ matches!(self, Self::Component(component) if component == rhs)
+ }
+}
+
+impl PartialEq<BorrowedFormatItem<'_>> for Component {
+ fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialEq<&[Self]> for BorrowedFormatItem<'_> {
+ fn eq(&self, rhs: &&[Self]) -> bool {
+ matches!(self, Self::Compound(compound) if compound == rhs)
+ }
+}
+
+impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] {
+ fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
+ rhs == self
+ }
+}
diff --git a/third_party/rust/time/src/format_description/component.rs b/third_party/rust/time/src/format_description/component.rs
new file mode 100644
index 0000000000..68d162e260
--- /dev/null
+++ b/third_party/rust/time/src/format_description/component.rs
@@ -0,0 +1,37 @@
+//! Part of a format description.
+
+use crate::format_description::modifier;
+
+/// A component of a larger format description.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Component {
+ /// Day of the month.
+ Day(modifier::Day),
+ /// Month of the year.
+ Month(modifier::Month),
+ /// Ordinal day of the year.
+ Ordinal(modifier::Ordinal),
+ /// Day of the week.
+ Weekday(modifier::Weekday),
+ /// Week within the year.
+ WeekNumber(modifier::WeekNumber),
+ /// Year of the date.
+ Year(modifier::Year),
+ /// Hour of the day.
+ Hour(modifier::Hour),
+ /// 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),
+}
diff --git a/third_party/rust/time/src/format_description/mod.rs b/third_party/rust/time/src/format_description/mod.rs
new file mode 100644
index 0000000000..7712288e74
--- /dev/null
+++ b/third_party/rust/time/src/format_description/mod.rs
@@ -0,0 +1,34 @@
+//! 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, the
+//! [`format_description::parse`](crate::format_description::parse()) function.
+
+mod borrowed_format_item;
+mod component;
+pub mod modifier;
+#[cfg(feature = "alloc")]
+mod owned_format_item;
+#[cfg(feature = "alloc")]
+mod parse;
+
+pub use borrowed_format_item::BorrowedFormatItem as FormatItem;
+#[cfg(feature = "alloc")]
+pub use owned_format_item::OwnedFormatItem;
+
+pub use self::component::Component;
+#[cfg(feature = "alloc")]
+pub use self::parse::{parse, parse_owned};
+
+/// 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;
+}
diff --git a/third_party/rust/time/src/format_description/modifier.rs b/third_party/rust/time/src/format_description/modifier.rs
new file mode 100644
index 0000000000..e01c18fda4
--- /dev/null
+++ b/third_party/rust/time/src/format_description/modifier.rs
@@ -0,0 +1,355 @@
+//! Various modifiers for components.
+
+// region: date modifiers
+/// Day of the month.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Day {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The representation of a month.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum MonthRepr {
+ /// The number of the month (January is 1, December is 12).
+ Numerical,
+ /// The long form of the month name (e.g. "January").
+ Long,
+ /// The short form of the month name (e.g. "Jan").
+ Short,
+}
+
+/// Month of the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Month {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What form of representation should be used?
+ pub repr: MonthRepr,
+ /// Is the value case sensitive when parsing?
+ pub case_sensitive: bool,
+}
+
+/// Ordinal day of the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Ordinal {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The representation used for the day of the week.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WeekdayRepr {
+ /// The short form of the weekday (e.g. "Mon").
+ Short,
+ /// The long form of the weekday (e.g. "Monday").
+ Long,
+ /// A numerical representation using Sunday as the first day of the week.
+ ///
+ /// Sunday is either 0 or 1, depending on the other modifier's value.
+ Sunday,
+ /// A numerical representation using Monday as the first day of the week.
+ ///
+ /// Monday is either 0 or 1, depending on the other modifier's value.
+ Monday,
+}
+
+/// Day of the week.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Weekday {
+ /// What form of representation should be used?
+ pub repr: WeekdayRepr,
+ /// When using a numerical representation, should it be zero or one-indexed?
+ pub one_indexed: bool,
+ /// Is the value case sensitive when parsing?
+ pub case_sensitive: bool,
+}
+
+/// The representation used for the week number.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WeekNumberRepr {
+ /// Week 1 is the week that contains January 4.
+ Iso,
+ /// Week 1 begins on the first Sunday of the calendar year.
+ Sunday,
+ /// Week 1 begins on the first Monday of the calendar year.
+ Monday,
+}
+
+/// Week within the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct WeekNumber {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What kind of representation should be used?
+ pub repr: WeekNumberRepr,
+}
+
+/// The representation used for a year value.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum YearRepr {
+ /// The full value of the year.
+ Full,
+ /// Only the last two digits of the year.
+ LastTwo,
+}
+
+/// Year of the date.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Year {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What kind of representation should be used?
+ pub repr: YearRepr,
+ /// Whether the value is based on the ISO week number or the Gregorian calendar.
+ pub iso_week_based: bool,
+ /// Whether the `+` sign is present when a positive year contains fewer than five digits.
+ pub sign_is_mandatory: bool,
+}
+// endregion date modifiers
+
+// region: time modifiers
+/// Hour of the day.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Hour {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// Is the hour displayed using a 12 or 24-hour clock?
+ pub is_12_hour_clock: bool,
+}
+
+/// Minute within the hour.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Minute {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// AM/PM part of the time.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Period {
+ /// Is the period uppercase or lowercase?
+ pub is_uppercase: bool,
+ /// Is the value case sensitive when parsing?
+ ///
+ /// Note that when `false`, the `is_uppercase` field has no effect on parsing behavior.
+ pub case_sensitive: bool,
+}
+
+/// Second within the minute.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Second {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The number of digits present in a subsecond representation.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SubsecondDigits {
+ /// Exactly one digit.
+ One,
+ /// Exactly two digits.
+ Two,
+ /// Exactly three digits.
+ Three,
+ /// Exactly four digits.
+ Four,
+ /// Exactly five digits.
+ Five,
+ /// Exactly six digits.
+ Six,
+ /// Exactly seven digits.
+ Seven,
+ /// Exactly eight digits.
+ Eight,
+ /// Exactly nine digits.
+ Nine,
+ /// Any number of digits (up to nine) that is at least one. When formatting, the minimum digits
+ /// necessary will be used.
+ OneOrMore,
+}
+
+/// Subsecond within the second.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Subsecond {
+ /// How many digits are present in the component?
+ pub digits: SubsecondDigits,
+}
+// endregion time modifiers
+
+// region: offset modifiers
+/// Hour of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetHour {
+ /// Whether the `+` sign is present on positive values.
+ pub sign_is_mandatory: bool,
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// Minute within the hour of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetMinute {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// Second within the minute of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetSecond {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+// endregion offset modifiers
+
+/// Type of padding to ensure a minimum width.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Padding {
+ /// A space character (` `) should be used as padding.
+ Space,
+ /// A zero character (`0`) should be used as padding.
+ Zero,
+ /// There is no padding. This can result in a width below the otherwise minimum number of
+ /// characters.
+ None,
+}
+
+/// Generate the provided code if and only if `pub` is present.
+macro_rules! if_pub {
+ (pub $(#[$attr:meta])*; $($x:tt)*) => {
+ $(#[$attr])*
+ ///
+ /// This function exists since [`Default::default()`] cannot be used in a `const` context.
+ /// It may be removed once that becomes possible. As the [`Default`] trait is in the
+ /// prelude, removing this function in the future will not cause any resolution failures for
+ /// the overwhelming majority of users; only users who use `#![no_implicit_prelude]` will be
+ /// affected. As such it will not be considered a breaking change.
+ $($x)*
+ };
+ ($($_:tt)*) => {};
+}
+
+/// Implement `Default` for the given type. This also generates an inherent implementation of a
+/// `default` method that is `const fn`, permitting the default value to be used in const contexts.
+// Every modifier should use this macro rather than a derived `Default`.
+macro_rules! impl_const_default {
+ ($($(#[$doc:meta])* $(@$pub:ident)? $type:ty => $default:expr;)*) => {$(
+ impl $type {
+ if_pub! {
+ $($pub)?
+ $(#[$doc])*;
+ pub const fn default() -> Self {
+ $default
+ }
+ }
+ }
+
+ $(#[$doc])*
+ impl Default for $type {
+ fn default() -> Self {
+ $default
+ }
+ }
+ )*};
+}
+
+impl_const_default! {
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Day => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the
+ /// [`Numerical`](Self::Numerical) representation.
+ MonthRepr => Self::Numerical;
+ /// Creates an instance of this type that indicates the value uses the
+ /// [`Numerical`](MonthRepr::Numerical) representation, is [padded with zeroes](Padding::Zero),
+ /// and is case-sensitive when parsing.
+ @pub Month => Self {
+ padding: Padding::Zero,
+ repr: MonthRepr::Numerical,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Ordinal => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the [`Long`](Self::Long) representation.
+ WeekdayRepr => Self::Long;
+ /// Creates a modifier that indicates the value uses the [`Long`](WeekdayRepr::Long)
+ /// representation and is case-sensitive when parsing. If the representation is changed to a
+ /// numerical one, the instance defaults to one-based indexing.
+ @pub Weekday => Self {
+ repr: WeekdayRepr::Long,
+ one_indexed: true,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates that the value uses the [`Iso`](Self::Iso) representation.
+ WeekNumberRepr => Self::Iso;
+ /// Creates a modifier that indicates that the value is [padded with zeroes](Padding::Zero)
+ /// and uses the [`Iso`](WeekNumberRepr::Iso) representation.
+ @pub WeekNumber => Self {
+ padding: Padding::Zero,
+ repr: WeekNumberRepr::Iso,
+ };
+ /// Creates a modifier that indicates the value uses the [`Full`](Self::Full) representation.
+ YearRepr => Self::Full;
+ /// Creates a modifier that indicates the value uses the [`Full`](YearRepr::Full)
+ /// representation, is [padded with zeroes](Padding::Zero), uses the Gregorian calendar as its
+ /// base, and only includes the year's sign if necessary.
+ @pub Year => Self {
+ padding: Padding::Zero,
+ repr: YearRepr::Full,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero) and
+ /// has the 24-hour representation.
+ @pub Hour => Self {
+ padding: Padding::Zero,
+ is_12_hour_clock: false,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Minute => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the upper-case representation and is
+ /// case-sensitive when parsing.
+ @pub Period => Self {
+ is_uppercase: true,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Second => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the stringified value contains [one or more
+ /// digits](Self::OneOrMore).
+ SubsecondDigits => Self::OneOrMore;
+ /// Creates a modifier that indicates the stringified value contains [one or more
+ /// digits](SubsecondDigits::OneOrMore).
+ @pub Subsecond => Self { digits: SubsecondDigits::OneOrMore };
+ /// Creates a modifier that indicates the value uses the `+` sign for all positive values
+ /// and is [padded with zeroes](Padding::Zero).
+ @pub OffsetHour => Self {
+ sign_is_mandatory: true,
+ padding: Padding::Zero,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub OffsetMinute => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub OffsetSecond => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero).
+ Padding => Self::Zero;
+}
diff --git a/third_party/rust/time/src/format_description/owned_format_item.rs b/third_party/rust/time/src/format_description/owned_format_item.rs
new file mode 100644
index 0000000000..6f7f7c2337
--- /dev/null
+++ b/third_party/rust/time/src/format_description/owned_format_item.rs
@@ -0,0 +1,162 @@
+//! 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::{Component, FormatItem};
+
+/// A complete description of how to format and parse a type.
+#[non_exhaustive]
+#[derive(Clone, PartialEq, Eq)]
+pub enum OwnedFormatItem {
+ /// Bytes that are formatted as-is.
+ ///
+ /// **Note**: If you call the `format` method that returns a `String`, these bytes will be
+ /// passed through `String::from_utf8_lossy`.
+ Literal(Box<[u8]>),
+ /// 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 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(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(),
+ }
+ }
+}
+
+// region: conversions from FormatItem
+impl From<FormatItem<'_>> for OwnedFormatItem {
+ fn from(item: FormatItem<'_>) -> Self {
+ (&item).into()
+ }
+}
+
+impl From<&FormatItem<'_>> for OwnedFormatItem {
+ fn from(item: &FormatItem<'_>) -> Self {
+ match item {
+ FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
+ FormatItem::Component(component) => Self::Component(*component),
+ FormatItem::Compound(compound) => Self::Compound(
+ compound
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
+ FormatItem::First(items) => Self::First(
+ items
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ }
+ }
+}
+
+impl From<Vec<FormatItem<'_>>> for OwnedFormatItem {
+ fn from(items: Vec<FormatItem<'_>>) -> Self {
+ items.as_slice().into()
+ }
+}
+
+impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem {
+ fn from(items: &T) -> Self {
+ Self::Compound(
+ items
+ .as_ref()
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ )
+ }
+}
+// endregion conversions from FormatItem
+
+// region: from variants
+impl From<Component> for OwnedFormatItem {
+ fn from(component: Component) -> Self {
+ Self::Component(component)
+ }
+}
+
+impl TryFrom<OwnedFormatItem> for Component {
+ type Error = error::DifferentVariant;
+
+ 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 {
+ fn from(items: Vec<Self>) -> Self {
+ Self::Compound(items.into_boxed_slice())
+ }
+}
+
+impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> {
+ type Error = error::DifferentVariant;
+
+ fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
+ match value {
+ OwnedFormatItem::Compound(items) => Ok(items.into_vec()),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+// endregion from variants
+
+// region: equality
+impl PartialEq<Component> for OwnedFormatItem {
+ fn eq(&self, rhs: &Component) -> bool {
+ matches!(self, Self::Component(component) if component == rhs)
+ }
+}
+
+impl PartialEq<OwnedFormatItem> for Component {
+ fn eq(&self, rhs: &OwnedFormatItem) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialEq<&[Self]> for OwnedFormatItem {
+ fn eq(&self, rhs: &&[Self]) -> bool {
+ matches!(self, Self::Compound(compound) if &&**compound == rhs)
+ }
+}
+
+impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] {
+ fn eq(&self, rhs: &OwnedFormatItem) -> bool {
+ rhs == self
+ }
+}
+// endregion equality
diff --git a/third_party/rust/time/src/format_description/parse/ast.rs b/third_party/rust/time/src/format_description/parse/ast.rs
new file mode 100644
index 0000000000..6977cc9cda
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/ast.rs
@@ -0,0 +1,278 @@
+//! AST for parsing format descriptions.
+
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::iter;
+use core::iter::Peekable;
+
+use super::{lexer, Error, Location, Span};
+
+/// One part of a complete format description.
+#[allow(variant_size_differences)]
+pub(super) enum Item<'a> {
+ /// A literal string, formatted and parsed as-is.
+ Literal {
+ /// The string itself.
+ value: &'a [u8],
+ /// Where the string originates from in the format string.
+ _span: Span,
+ },
+ /// A sequence of brackets. The first acts as the escape character.
+ EscapedBracket {
+ /// The first bracket.
+ _first: Location,
+ /// The second bracket.
+ _second: Location,
+ },
+ /// Part of a type, along with its modifiers.
+ Component {
+ /// Where the opening bracket was in the format string.
+ _opening_bracket: Location,
+ /// Whitespace between the opening bracket and name.
+ _leading_whitespace: Option<Whitespace<'a>>,
+ /// The name of the component.
+ name: Name<'a>,
+ /// The modifiers for the component.
+ modifiers: Vec<Modifier<'a>>,
+ /// Whitespace between the modifiers and closing bracket.
+ _trailing_whitespace: Option<Whitespace<'a>>,
+ /// Where the closing bracket was in the format string.
+ _closing_bracket: Location,
+ },
+}
+
+/// Whitespace within a component.
+pub(super) struct Whitespace<'a> {
+ /// The whitespace itself.
+ pub(super) _value: &'a [u8],
+ /// Where the whitespace was in the format string.
+ pub(super) span: Span,
+}
+
+/// The name of a component.
+pub(super) struct Name<'a> {
+ /// The name itself.
+ pub(super) value: &'a [u8],
+ /// Where the name was in the format string.
+ pub(super) span: Span,
+}
+
+/// A modifier for a component.
+pub(super) struct Modifier<'a> {
+ /// Whitespace preceding the modifier.
+ pub(super) _leading_whitespace: Whitespace<'a>,
+ /// The key of the modifier.
+ pub(super) key: Key<'a>,
+ /// Where the colon of the modifier was in the format string.
+ pub(super) _colon: Location,
+ /// The value of the modifier.
+ pub(super) value: Value<'a>,
+}
+
+/// The key of a modifier.
+pub(super) struct Key<'a> {
+ /// The key itself.
+ pub(super) value: &'a [u8],
+ /// Where the key was in the format string.
+ pub(super) span: Span,
+}
+
+/// The value of a modifier.
+pub(super) struct Value<'a> {
+ /// The value itself.
+ pub(super) value: &'a [u8],
+ /// Where the value was in the format string.
+ pub(super) span: Span,
+}
+
+/// Parse the provided tokens into an AST.
+pub(super) fn parse<'a>(
+ tokens: impl Iterator<Item = lexer::Token<'a>>,
+) -> impl Iterator<Item = Result<Item<'a>, Error>> {
+ let mut tokens = tokens.peekable();
+ iter::from_fn(move || {
+ Some(match tokens.next()? {
+ lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }),
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Opening,
+ location,
+ } => {
+ // escaped bracket
+ if let Some(&lexer::Token::Bracket {
+ kind: lexer::BracketKind::Opening,
+ location: second_location,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ Ok(Item::EscapedBracket {
+ _first: location,
+ _second: second_location,
+ })
+ }
+ // component
+ else {
+ parse_component(location, &mut tokens)
+ }
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } => unreachable!(
+ "internal error: closing bracket should have been consumed by `parse_component`",
+ ),
+ lexer::Token::ComponentPart {
+ kind: _,
+ value: _,
+ span: _,
+ } => unreachable!(
+ "internal error: component part should have been consumed by `parse_component`",
+ ),
+ })
+ })
+}
+
+/// Parse a component. This assumes that the opening bracket has already been consumed.
+fn parse_component<'a>(
+ opening_bracket: Location,
+ tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>,
+) -> Result<Item<'a>, Error> {
+ let leading_whitespace = if let Some(&lexer::Token::ComponentPart {
+ kind: lexer::ComponentKind::Whitespace,
+ value,
+ span,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ Some(Whitespace {
+ _value: value,
+ span,
+ })
+ } else {
+ None
+ };
+
+ let name = if let Some(&lexer::Token::ComponentPart {
+ kind: lexer::ComponentKind::NotWhitespace,
+ value,
+ span,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ Name { value, span }
+ } else {
+ let span = leading_whitespace.map_or_else(
+ || Span {
+ start: opening_bracket,
+ end: opening_bracket,
+ },
+ |whitespace| whitespace.span.shrink_to_end(),
+ );
+ return Err(Error {
+ _inner: span.error("expected component name"),
+ public: crate::error::InvalidFormatDescription::MissingComponentName {
+ index: span.start_byte(),
+ },
+ });
+ };
+
+ let mut modifiers = Vec::new();
+ let trailing_whitespace = loop {
+ let whitespace = if let Some(&lexer::Token::ComponentPart {
+ kind: lexer::ComponentKind::Whitespace,
+ value,
+ span,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ Whitespace {
+ _value: value,
+ span,
+ }
+ } else {
+ break None;
+ };
+
+ if let Some(&lexer::Token::ComponentPart {
+ kind: lexer::ComponentKind::NotWhitespace,
+ value,
+ span,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+
+ let colon_index = match value.iter().position(|&b| b == b':') {
+ Some(index) => index,
+ None => {
+ return Err(Error {
+ _inner: span.error("modifier must be of the form `key:value`"),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: span.start_byte(),
+ },
+ });
+ }
+ };
+ let key = &value[..colon_index];
+ let value = &value[colon_index + 1..];
+
+ if key.is_empty() {
+ return Err(Error {
+ _inner: span.shrink_to_start().error("expected modifier key"),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.start_byte(),
+ },
+ });
+ }
+ if value.is_empty() {
+ return Err(Error {
+ _inner: span.shrink_to_end().error("expected modifier value"),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.shrink_to_end().start_byte(),
+ },
+ });
+ }
+
+ modifiers.push(Modifier {
+ _leading_whitespace: whitespace,
+ key: Key {
+ value: key,
+ span: span.subspan(..colon_index),
+ },
+ _colon: span.start.offset(colon_index),
+ value: Value {
+ value,
+ span: span.subspan(colon_index + 1..),
+ },
+ });
+ } else {
+ break Some(whitespace);
+ }
+ };
+
+ let closing_bracket = if let Some(&lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ location
+ } else {
+ return Err(Error {
+ _inner: opening_bracket.error("unclosed bracket"),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte,
+ },
+ });
+ };
+
+ Ok(Item::Component {
+ _opening_bracket: opening_bracket,
+ _leading_whitespace: leading_whitespace,
+ name,
+ modifiers,
+ _trailing_whitespace: trailing_whitespace,
+ _closing_bracket: closing_bracket,
+ })
+}
diff --git a/third_party/rust/time/src/format_description/parse/format_item.rs b/third_party/rust/time/src/format_description/parse/format_item.rs
new file mode 100644
index 0000000000..53146d5228
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/format_item.rs
@@ -0,0 +1,386 @@
+//! Typed, validated representation of a parsed format description.
+
+use alloc::string::String;
+
+use super::{ast, Error};
+
+/// Parse an AST iterator into a sequence of format items.
+pub(super) fn parse<'a>(
+ ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
+) -> impl Iterator<Item = Result<Item<'a>, Error>> {
+ ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
+}
+
+/// A description of how to format and parse one part of a type.
+#[allow(variant_size_differences)]
+pub(super) enum Item<'a> {
+ /// A literal string.
+ Literal(&'a [u8]),
+ /// Part of a type, along with its modifiers.
+ Component(Component),
+}
+
+impl Item<'_> {
+ /// Parse an AST item into a format item.
+ pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
+ Ok(match ast_item {
+ ast::Item::Component {
+ _opening_bracket: _,
+ _leading_whitespace: _,
+ name,
+ modifiers,
+ _trailing_whitespace: _,
+ _closing_bracket: _,
+ } => Item::Component(component_from_ast(&name, &modifiers)?),
+ ast::Item::Literal { value, _span: _ } => Item::Literal(value),
+ ast::Item::EscapedBracket {
+ _first: _,
+ _second: _,
+ } => Item::Literal(b"["),
+ })
+ }
+}
+
+impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> {
+ fn from(item: Item<'a>) -> Self {
+ match item {
+ Item::Literal(literal) => Self::Literal(literal),
+ Item::Component(component) => Self::Component(component.into()),
+ }
+ }
+}
+
+impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
+ fn from(item: Item<'_>) -> Self {
+ match item {
+ Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
+ Item::Component(component) => Self::Component(component.into()),
+ }
+ }
+}
+
+/// Declare the `Component` struct.
+macro_rules! component_definition {
+ ($vis:vis enum $name:ident {
+ $($variant:ident = $parse_variant:literal {
+ $($field:ident = $parse_field:literal:
+ Option<$field_type:ty> => $target_field:ident),* $(,)?
+ }),* $(,)?
+ }) => {
+ $vis enum $name {
+ $($variant($variant),)*
+ }
+
+ $($vis struct $variant {
+ $($field: Option<$field_type>),*
+ })*
+
+ $(impl $variant {
+ /// Parse the component from the AST, given its modifiers.
+ fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> Result<Self, Error> {
+ let mut this = Self {
+ $($field: None),*
+ };
+
+ for modifier in modifiers {
+ $(if modifier.key.value.eq_ignore_ascii_case($parse_field) {
+ this.$field = <$field_type>::from_modifier_value(&modifier.value)?;
+ continue;
+ })*
+ return Err(Error {
+ _inner: modifier.key.span.error("invalid modifier key"),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(modifier.key.value).into_owned(),
+ index: modifier.key.span.start_byte(),
+ }
+ });
+ }
+
+ Ok(this)
+ }
+ })*
+
+ impl From<$name> for crate::format_description::Component {
+ fn from(component: $name) -> Self {
+ match component {$(
+ $name::$variant($variant { $($field),* }) => {
+ $crate::format_description::component::Component::$variant(
+ $crate::format_description::modifier::$variant {$(
+ $target_field: $field.unwrap_or_default().into()
+ ),*}
+ )
+ }
+ )*}
+ }
+ }
+
+ /// Parse a component from the AST, given its name and modifiers.
+ fn component_from_ast(
+ name: &ast::Name<'_>,
+ modifiers: &[ast::Modifier<'_>],
+ ) -> Result<Component, Error> {
+ $(if name.value.eq_ignore_ascii_case($parse_variant) {
+ return Ok(Component::$variant($variant::with_modifiers(&modifiers)?));
+ })*
+ Err(Error {
+ _inner: name.span.error("invalid component"),
+ public: crate::error::InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(name.value).into_owned(),
+ index: name.span.start_byte(),
+ },
+ })
+ }
+ }
+}
+
+// Keep in alphabetical order.
+component_definition! {
+ pub(super) enum Component {
+ Day = b"day" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Hour = b"hour" {
+ padding = b"padding": Option<Padding> => padding,
+ base = b"repr": Option<HourBase> => is_12_hour_clock,
+ },
+ Minute = b"minute" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Month = b"month" {
+ padding = b"padding": Option<Padding> => padding,
+ repr = b"repr": Option<MonthRepr> => repr,
+ case_sensitive = b"case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
+ },
+ OffsetHour = b"offset_hour" {
+ sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory,
+ padding = b"padding": Option<Padding> => padding,
+ },
+ OffsetMinute = b"offset_minute" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ OffsetSecond = b"offset_second" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Ordinal = b"ordinal" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Period = b"period" {
+ case = b"case": Option<PeriodCase> => is_uppercase,
+ case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
+ },
+ Second = b"second" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Subsecond = b"subsecond" {
+ digits = b"digits": Option<SubsecondDigits> => digits,
+ },
+ Weekday = b"weekday" {
+ repr = b"repr": Option<WeekdayRepr> => repr,
+ one_indexed = b"one_indexed": Option<WeekdayOneIndexed> => one_indexed,
+ case_sensitive = b"case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
+ },
+ WeekNumber = b"week_number" {
+ padding = b"padding": Option<Padding> => padding,
+ repr = b"repr": Option<WeekNumberRepr> => repr,
+ },
+ Year = b"year" {
+ padding = b"padding": Option<Padding> => padding,
+ repr = b"repr": Option<YearRepr> => repr,
+ base = b"base": Option<YearBase> => iso_week_based,
+ sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory,
+ },
+ }
+}
+
+/// 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
+ };
+}
+
+// TODO use `#[derive(Default)]` on enums once MSRV is 1.62 (NET 2022-12-30)
+/// Simulate `#[derive(Default)]` on enums.
+macro_rules! derived_default_on_enum {
+ ($type:ty; $default:expr) => {};
+ ($attr:meta $type:ty; $default:expr) => {
+ impl Default for $type {
+ fn default() -> Self {
+ $default
+ }
+ }
+ };
+}
+
+/// 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 {
+ ($(
+ enum $name:ident $(($target_ty:ty))? {
+ $(
+ $(#[$attr:meta])?
+ $variant:ident $(($target_value:expr))? = $parse_variant:literal
+ ),* $(,)?
+ }
+ )+) => {$(
+ enum $name {
+ $($variant),*
+ }
+
+ $(derived_default_on_enum! {
+ $($attr)? $name; $name::$variant
+ })*
+
+ impl $name {
+ /// Parse the modifier from its string representation.
+ fn from_modifier_value(value: &ast::Value<'_>) -> Result<Option<Self>, Error> {
+ $(if value.value.eq_ignore_ascii_case($parse_variant) {
+ return Ok(Some(Self::$variant));
+ })*
+ Err(Error {
+ _inner: value.span.error("invalid modifier value"),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value.value).into_owned(),
+ index: value.span.start_byte(),
+ },
+ })
+ }
+ }
+
+ impl From<$name> for target_ty!($name $($target_ty)?) {
+ 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) = b"12",
+ #[default]
+ TwentyFour(false) = b"24",
+ }
+
+ enum MonthCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum MonthRepr {
+ #[default]
+ Numerical = b"numerical",
+ Long = b"long",
+ Short = b"short",
+ }
+
+ enum Padding {
+ Space = b"space",
+ #[default]
+ Zero = b"zero",
+ None = b"none",
+ }
+
+ enum PeriodCase(bool) {
+ Lower(false) = b"lower",
+ #[default]
+ Upper(true) = b"upper",
+ }
+
+ enum PeriodCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum SignBehavior(bool) {
+ #[default]
+ Automatic(false) = b"automatic",
+ Mandatory(true) = b"mandatory",
+ }
+
+ enum SubsecondDigits {
+ One = b"1",
+ Two = b"2",
+ Three = b"3",
+ Four = b"4",
+ Five = b"5",
+ Six = b"6",
+ Seven = b"7",
+ Eight = b"8",
+ Nine = b"9",
+ #[default]
+ OneOrMore = b"1+",
+ }
+
+ enum WeekNumberRepr {
+ #[default]
+ Iso = b"iso",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum WeekdayCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayOneIndexed(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayRepr {
+ Short = b"short",
+ #[default]
+ Long = b"long",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum YearBase(bool) {
+ #[default]
+ Calendar(false) = b"calendar",
+ IsoWeek(true) = b"iso_week",
+ }
+
+ enum YearRepr {
+ #[default]
+ Full = b"full",
+ LastTwo = b"last_two",
+ }
+}
diff --git a/third_party/rust/time/src/format_description/parse/lexer.rs b/third_party/rust/time/src/format_description/parse/lexer.rs
new file mode 100644
index 0000000000..e405ea8c85
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/lexer.rs
@@ -0,0 +1,159 @@
+//! Lexer for parsing format descriptions.
+
+use core::iter;
+
+use super::{Location, Span};
+
+/// A token emitted by the lexer. There is no semantic meaning at this stage.
+pub(super) enum Token<'a> {
+ /// A literal string, formatted and parsed as-is.
+ Literal {
+ /// The string itself.
+ value: &'a [u8],
+ /// Where the string was in the format string.
+ span: Span,
+ },
+ /// An opening or closing bracket. May or may not be the start or end of a component.
+ Bracket {
+ /// Whether the bracket is opening or closing.
+ kind: BracketKind,
+ /// Where the bracket was in the format string.
+ location: Location,
+ },
+ /// One part of a component. This could be its name, a modifier, or whitespace.
+ ComponentPart {
+ /// Whether the part is whitespace or not.
+ kind: ComponentKind,
+ /// The part itself.
+ value: &'a [u8],
+ /// Where the part was in the format string.
+ span: Span,
+ },
+}
+
+/// What type of bracket is present.
+pub(super) enum BracketKind {
+ /// An opening bracket: `[`
+ Opening,
+ /// A closing bracket: `]`
+ Closing,
+}
+
+/// Indicates whether the component is whitespace or not.
+pub(super) enum ComponentKind {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Whitespace,
+ #[allow(clippy::missing_docs_in_private_items)]
+ NotWhitespace,
+}
+
+/// Attach [`Location`] information to each byte in the iterator.
+fn attach_location(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = (u8, Location)> {
+ let mut line = 1;
+ let mut column = 1;
+ let mut byte_pos = 0;
+
+ iter.map(move |byte| {
+ let location = Location {
+ line,
+ column,
+ byte: byte_pos,
+ };
+ column += 1;
+ byte_pos += 1;
+
+ if byte == b'\n' {
+ line += 1;
+ column = 1;
+ }
+
+ (byte, location)
+ })
+}
+
+/// Parse the string into a series of [`Token`]s.
+pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> {
+ let mut depth: u8 = 0;
+ let mut iter = attach_location(input.iter().copied()).peekable();
+ let mut second_bracket_location = None;
+
+ iter::from_fn(move || {
+ // There is a flag set to emit the second half of an escaped bracket pair.
+ if let Some(location) = second_bracket_location.take() {
+ return Some(Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ });
+ }
+
+ Some(match iter.next()? {
+ (b'[', location) => {
+ if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') {
+ // escaped bracket
+ second_bracket_location = Some(second_location);
+ input = &input[2..];
+ } else {
+ // opening bracket
+ depth += 1;
+ input = &input[1..];
+ }
+
+ Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }
+ }
+ // closing bracket
+ (b']', location) if depth > 0 => {
+ depth -= 1;
+ input = &input[1..];
+ Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ }
+ }
+ // literal
+ (_, start_location) if depth == 0 => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+
+ while let Some((_, location)) = iter.next_if(|&(byte, _)| byte != b'[') {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+ Token::Literal {
+ value,
+ span: Span::start_end(start_location, end_location),
+ }
+ }
+ // component part
+ (byte, start_location) => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+ let is_whitespace = byte.is_ascii_whitespace();
+
+ while let Some((_, location)) = iter.next_if(|&(byte, _)| {
+ byte != b'[' && byte != b']' && is_whitespace == byte.is_ascii_whitespace()
+ }) {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+ Token::ComponentPart {
+ kind: if is_whitespace {
+ ComponentKind::Whitespace
+ } else {
+ ComponentKind::NotWhitespace
+ },
+ value,
+ span: Span::start_end(start_location, end_location),
+ }
+ }
+ })
+ })
+}
diff --git a/third_party/rust/time/src/format_description/parse/mod.rs b/third_party/rust/time/src/format_description/parse/mod.rs
new file mode 100644
index 0000000000..c73a674494
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/mod.rs
@@ -0,0 +1,193 @@
+//! Parser for format descriptions.
+
+use alloc::vec::Vec;
+use core::ops::{RangeFrom, RangeTo};
+
+mod ast;
+mod format_item;
+mod lexer;
+
+/// 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).
+pub fn parse(
+ s: &str,
+) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
+{
+ let lexed = lexer::lex(s.as_bytes());
+ let ast = ast::parse(lexed);
+ let format_items = format_item::parse(ast);
+ Ok(format_items
+ .map(|res| res.map(Into::into))
+ .collect::<Result<Vec<_>, _>>()?)
+}
+
+/// 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).
+///
+/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
+/// that there is no lifetime that needs to be handled.
+///
+/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
+pub fn parse_owned(
+ s: &str,
+) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> {
+ let lexed = lexer::lex(s.as_bytes());
+ let ast = ast::parse(lexed);
+ let format_items = format_item::parse(ast);
+ let items = format_items
+ .map(|res| res.map(Into::into))
+ .collect::<Result<Vec<_>, _>>()?
+ .into_boxed_slice();
+ Ok(crate::format_description::OwnedFormatItem::Compound(items))
+}
+
+/// A location within a string.
+#[derive(Clone, Copy)]
+struct Location {
+ /// The one-indexed line of the string.
+ line: usize,
+ /// The one-indexed column of the string.
+ column: usize,
+ /// The zero-indexed byte of the string.
+ byte: usize,
+}
+
+impl Location {
+ /// 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"]
+ const fn offset(&self, offset: usize) -> Self {
+ Self {
+ line: self.line,
+ column: self.column + offset,
+ byte: self.byte + offset,
+ }
+ }
+
+ /// Create an error with the provided message at this location.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _span: Span {
+ start: self,
+ end: self,
+ },
+ }
+ }
+}
+
+/// A start and end point within a string.
+#[derive(Clone, Copy)]
+struct Span {
+ #[allow(clippy::missing_docs_in_private_items)]
+ start: Location,
+ #[allow(clippy::missing_docs_in_private_items)]
+ end: Location,
+}
+
+impl Span {
+ /// Create a new `Span` from the provided start and end locations.
+ const fn start_end(start: Location, end: Location) -> Self {
+ Self { start, end }
+ }
+
+ /// Reduce this span to the provided range.
+ #[must_use = "this does not modify the original value"]
+ fn subspan(&self, range: impl Subspan) -> Self {
+ range.subspan(self)
+ }
+
+ /// Obtain a `Span` pointing at the start of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ 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.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _span: self,
+ }
+ }
+
+ /// Get the byte index that the span starts at.
+ const fn start_byte(&self) -> usize {
+ self.start.byte
+ }
+}
+
+/// A trait for types that can be used to reduce a `Span`.
+trait Subspan {
+ /// Reduce the provided `Span` to a new `Span`.
+ fn subspan(self, span: &Span) -> Span;
+}
+
+impl Subspan for RangeFrom<usize> {
+ fn subspan(self, span: &Span) -> Span {
+ assert_eq!(span.start.line, span.end.line);
+
+ Span {
+ start: Location {
+ line: span.start.line,
+ column: span.start.column + self.start,
+ byte: span.start.byte + self.start,
+ },
+ end: span.end,
+ }
+ }
+}
+
+impl Subspan for RangeTo<usize> {
+ fn subspan(self, span: &Span) -> Span {
+ assert_eq!(span.start.line, span.end.line);
+
+ Span {
+ start: span.start,
+ end: Location {
+ line: span.start.line,
+ column: span.start.column + self.end - 1,
+ byte: span.start.byte + self.end - 1,
+ },
+ }
+ }
+}
+
+/// 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: ErrorInner,
+ /// The error needed for interoperability with the rest of `time`.
+ public: crate::error::InvalidFormatDescription,
+}
+
+impl From<Error> for crate::error::InvalidFormatDescription {
+ fn from(error: Error) -> Self {
+ error.public
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/iso8601.rs b/third_party/rust/time/src/format_description/well_known/iso8601.rs
new file mode 100644
index 0000000000..f19181a926
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/iso8601.rs
@@ -0,0 +1,233 @@
+//! The format described in ISO 8601.
+
+mod adt_hack;
+
+use core::num::NonZeroU8;
+
+pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig};
+
+/// A configuration for [`Iso8601`] that only parses values.
+const PARSING_ONLY: EncodedConfig = Config {
+ formatted_components: FormattedComponents::None,
+ use_separators: false,
+ year_is_six_digits: false,
+ date_kind: DateKind::Calendar,
+ time_precision: TimePrecision::Hour {
+ decimal_digits: None,
+ },
+ offset_precision: OffsetPrecision::Hour,
+}
+.encode();
+
+/// The default configuration for [`Iso8601`].
+const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();
+
+/// 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 = DEFAULT_CONFIG>;
+
+impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Iso8601")
+ .field("config", &Config::decode(CONFIG))
+ .finish()
+ }
+}
+
+impl Iso8601<DEFAULT_CONFIG> {
+ /// 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 [`Config::DEFAULT`] and [`Config`]'s methods to create
+ /// a custom configuration.
+ pub const DEFAULT: Self = Self;
+}
+
+impl Iso8601<PARSING_ONLY> {
+ /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
+ /// unspecified behavior.
+ pub const PARSING: Self = Self;
+}
+
+/// 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 {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+ /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
+ /// number of decimal digits, if any.
+ Minute {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+ /// Format the hour, minute, and second. Nanoseconds will be represented with the specified
+ /// number of decimal digits, if any.
+ Second {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+}
+
+/// 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.
+#[allow(missing_copy_implementations)]
+#[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: NonZeroU8::new(9),
+ },
+ offset_precision: OffsetPrecision::Minute,
+ };
+
+ /// Set whether the format the date, time, and/or UTC offset.
+ pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
+ Self {
+ formatted_components,
+ ..self
+ }
+ }
+
+ /// Set whether the format contains separators (such as `-` or `:`).
+ pub const fn set_use_separators(self, use_separators: bool) -> Self {
+ Self {
+ use_separators,
+ ..self
+ }
+ }
+
+ /// Set whether the year is six digits.
+ 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.
+ 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.
+ pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
+ Self {
+ time_precision,
+ ..self
+ }
+ }
+
+ /// Set the precision for the UTC offset.
+ pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
+ Self {
+ offset_precision,
+ ..self
+ }
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
new file mode 100644
index 0000000000..757a68b18f
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
@@ -0,0 +1,249 @@
+//! Hackery to work around not being able to use ADTs in const generics on stable.
+
+use core::num::NonZeroU8;
+
+#[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`].
+///
+/// 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 _;
+ bytes[2] = self.year_is_six_digits as _;
+ 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: NonZeroU8::new(bytes[5]),
+ },
+ 1 => TimePrecision::Minute {
+ decimal_digits: NonZeroU8::new(bytes[5]),
+ },
+ 2 => TimePrecision::Second {
+ decimal_digits: NonZeroU8::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 {
+ assert!(bytes[idx] == 0, "invalid configuration");
+ idx += 1;
+ }
+
+ Self {
+ formatted_components,
+ use_separators,
+ year_is_six_digits,
+ date_kind,
+ time_precision,
+ offset_precision,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! eq {
+ ($a:expr, $b:expr) => {{
+ let a = $a;
+ let b = $b;
+ a.formatted_components == b.formatted_components
+ && a.use_separators == b.use_separators
+ && a.year_is_six_digits == b.year_is_six_digits
+ && a.date_kind == b.date_kind
+ && a.time_precision == b.time_precision
+ && a.offset_precision == b.offset_precision
+ }};
+ }
+
+ #[test]
+ fn encoding_roundtrip() {
+ macro_rules! assert_roundtrip {
+ ($config:expr) => {
+ let config = $config;
+ let encoded = config.encode();
+ let decoded = Config::decode(encoded);
+ assert!(eq!(config, decoded));
+ };
+ }
+
+ assert_roundtrip!(Config::DEFAULT);
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset));
+ assert_roundtrip!(Config::DEFAULT.set_use_separators(false));
+ assert_roundtrip!(Config::DEFAULT.set_use_separators(true));
+ assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false));
+ assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour));
+ assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute));
+ }
+
+ macro_rules! assert_decode_fail {
+ ($encoding:expr) => {
+ assert!(
+ std::panic::catch_unwind(|| {
+ Config::decode($encoding);
+ })
+ .is_err()
+ );
+ };
+ }
+
+ #[test]
+ fn decode_fail() {
+ assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00);
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/rfc2822.rs b/third_party/rust/time/src/format_description/well_known/rfc2822.rs
new file mode 100644
index 0000000000..3c890ab107
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/rfc2822.rs
@@ -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;
diff --git a/third_party/rust/time/src/format_description/well_known/rfc3339.rs b/third_party/rust/time/src/format_description/well_known/rfc3339.rs
new file mode 100644
index 0000000000..f0873cbac5
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/rfc3339.rs
@@ -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;
diff --git a/third_party/rust/time/src/formatting/formattable.rs b/third_party/rust/time/src/formatting/formattable.rs
new file mode 100644
index 0000000000..7fee2fbeab
--- /dev/null
+++ b/third_party/rust/time/src/formatting/formattable.rs
@@ -0,0 +1,304 @@
+//! A trait that can be used to format an item from its components.
+
+use core::ops::Deref;
+use std::io;
+
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
+use crate::format_description::{FormatItem, OwnedFormatItem};
+use crate::formatting::{
+ format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
+};
+use crate::{error, Date, Time, UtcOffset};
+
+/// A type that can be formatted.
+#[cfg_attr(__time_03_docs, doc(notable_trait))]
+pub trait Formattable: sealed::Sealed {}
+impl Formattable for FormatItem<'_> {}
+impl Formattable for [FormatItem<'_>] {}
+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: Deref> Formattable for T where T::Target: Formattable {}
+
+/// Seal the trait to prevent downstream users from implementing it.
+mod sealed {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Format the item using a format description, the intended output, and the various components.
+ pub trait Sealed {
+ /// Format the item into the provided output, returning the number of bytes written.
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format>;
+
+ /// Format the item directly to a `String`.
+ fn format(
+ &self,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<String, error::Format> {
+ let mut buf = Vec::new();
+ self.format_into(&mut buf, date, time, offset)?;
+ Ok(String::from_utf8_lossy(&buf).into_owned())
+ }
+ }
+}
+
+// region: custom formats
+impl<'a> sealed::Sealed for FormatItem<'a> {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ Ok(match *self {
+ Self::Literal(literal) => write(output, literal)?,
+ Self::Component(component) => format_component(output, component, date, time, offset)?,
+ Self::Compound(items) => items.format_into(output, date, time, offset)?,
+ Self::Optional(item) => item.format_into(output, date, time, offset)?,
+ Self::First(items) => match items {
+ [] => 0,
+ [item, ..] => item.format_into(output, date, time, offset)?,
+ },
+ })
+ }
+}
+
+impl<'a> sealed::Sealed for [FormatItem<'a>] {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+ for item in self.iter() {
+ bytes += item.format_into(output, date, time, offset)?;
+ }
+ Ok(bytes)
+ }
+}
+
+impl sealed::Sealed for OwnedFormatItem {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ match self {
+ Self::Literal(literal) => Ok(write(output, literal)?),
+ Self::Component(component) => format_component(output, *component, date, time, offset),
+ Self::Compound(items) => items.format_into(output, date, time, offset),
+ Self::Optional(item) => item.format_into(output, date, time, offset),
+ Self::First(items) => match &**items {
+ [] => Ok(0),
+ [item, ..] => item.format_into(output, date, time, offset),
+ },
+ }
+ }
+}
+
+impl sealed::Sealed for [OwnedFormatItem] {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+ for item in self.iter() {
+ bytes += item.format_into(output, date, time, offset)?;
+ }
+ Ok(bytes)
+ }
+}
+
+impl<T: Deref> sealed::Sealed for T
+where
+ T::Target: sealed::Sealed,
+{
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ self.deref().format_into(output, date, time, offset)
+ }
+}
+// endregion custom formats
+
+// region: well-known formats
+impl sealed::Sealed for Rfc2822 {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+
+ let mut bytes = 0;
+
+ let (year, month, day) = date.to_calendar_date();
+
+ if year < 1900 {
+ return Err(error::Format::InvalidComponent("year"));
+ }
+ if offset.seconds_past_minute() != 0 {
+ return Err(error::Format::InvalidComponent("offset_second"));
+ }
+
+ bytes += write(
+ output,
+ &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
+ )?;
+ bytes += write(output, b", ")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, day)?;
+ bytes += write(output, b" ")?;
+ bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?;
+ bytes += write(output, b" ")?;
+ bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
+ bytes += write(output, b" ")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.second())?;
+ bytes += write(output, b" ")?;
+ bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
+ bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?;
+ bytes +=
+ format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?;
+
+ Ok(bytes)
+ }
+}
+
+impl sealed::Sealed for Rfc3339 {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+
+ let mut bytes = 0;
+
+ let year = date.year();
+
+ if !(0..10_000).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ }
+ if offset.seconds_past_minute() != 0 {
+ return Err(error::Format::InvalidComponent("offset_second"));
+ }
+
+ bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
+ bytes += write(output, b"-")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, date.month() as u8)?;
+ bytes += write(output, b"-")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, date.day())?;
+ bytes += write(output, b"T")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.second())?;
+
+ #[allow(clippy::if_not_else)]
+ if time.nanosecond() != 0 {
+ let nanos = time.nanosecond();
+ bytes += write(output, b".")?;
+ bytes += if nanos % 10 != 0 {
+ format_number_pad_zero::<9, _, _>(output, nanos)
+ } else if (nanos / 10) % 10 != 0 {
+ format_number_pad_zero::<8, _, _>(output, nanos / 10)
+ } else if (nanos / 100) % 10 != 0 {
+ format_number_pad_zero::<7, _, _>(output, nanos / 100)
+ } else if (nanos / 1_000) % 10 != 0 {
+ format_number_pad_zero::<6, _, _>(output, nanos / 1_000)
+ } else if (nanos / 10_000) % 10 != 0 {
+ format_number_pad_zero::<5, _, _>(output, nanos / 10_000)
+ } else if (nanos / 100_000) % 10 != 0 {
+ format_number_pad_zero::<4, _, _>(output, nanos / 100_000)
+ } else if (nanos / 1_000_000) % 10 != 0 {
+ format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000)
+ } else if (nanos / 10_000_000) % 10 != 0 {
+ format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
+ } else {
+ format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
+ }?;
+ }
+
+ if offset == UtcOffset::UTC {
+ bytes += write(output, b"Z")?;
+ return Ok(bytes);
+ }
+
+ bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
+ bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?;
+ bytes += write(output, b":")?;
+ bytes +=
+ format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?;
+
+ Ok(bytes)
+ }
+}
+
+impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+
+ if Self::FORMAT_DATE {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ bytes += iso8601::format_date::<_, CONFIG>(output, date)?;
+ }
+ if Self::FORMAT_TIME {
+ let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
+ bytes += iso8601::format_time::<_, CONFIG>(output, time)?;
+ }
+ if Self::FORMAT_OFFSET {
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+ bytes += iso8601::format_offset::<_, CONFIG>(output, offset)?;
+ }
+
+ if bytes == 0 {
+ // The only reason there would be no bytes written is if the format was only for
+ // parsing.
+ panic!("attempted to format a parsing-only format description");
+ }
+
+ Ok(bytes)
+ }
+}
+// endregion well-known formats
diff --git a/third_party/rust/time/src/formatting/iso8601.rs b/third_party/rust/time/src/formatting/iso8601.rs
new file mode 100644
index 0000000000..1724f96f5c
--- /dev/null
+++ b/third_party/rust/time/src/formatting/iso8601.rs
@@ -0,0 +1,139 @@
+//! Helpers for implementing formatting for ISO 8601.
+
+use std::io;
+
+use crate::format_description::well_known::iso8601::{
+ DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
+};
+use crate::format_description::well_known::Iso8601;
+use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else};
+use crate::{error, Date, Time, UtcOffset};
+
+/// Format the date portion of ISO 8601.
+pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>(
+ output: &mut W,
+ date: Date,
+) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+
+ match Iso8601::<CONFIG>::DATE_KIND {
+ DateKind::Calendar => {
+ let (year, month, day) = date.to_calendar_date();
+ if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
+ bytes += write_if_else(output, year < 0, b"-", b"+")?;
+ bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
+ }
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, month as u8)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, day)?;
+ }
+ DateKind::Week => {
+ let (year, week, day) = date.to_iso_week_date();
+ if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
+ bytes += write_if_else(output, year < 0, b"-", b"+")?;
+ bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
+ }
+ bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, week)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ bytes += format_number_pad_zero::<1, _, _>(output, day.number_from_monday())?;
+ }
+ DateKind::Ordinal => {
+ let (year, day) = date.to_ordinal_date();
+ if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
+ bytes += write_if_else(output, year < 0, b"-", b"+")?;
+ bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
+ }
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ bytes += format_number_pad_zero::<3, _, _>(output, day)?;
+ }
+ }
+
+ Ok(bytes)
+}
+
+/// Format the time portion of ISO 8601.
+pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>(
+ output: &mut W,
+ time: Time,
+) -> Result<usize, error::Format> {
+ 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,
+ b"T",
+ )?;
+
+ let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano();
+
+ match Iso8601::<CONFIG>::TIME_PRECISION {
+ TimePrecision::Hour { decimal_digits } => {
+ let hours = (hours as f64)
+ + (minutes as f64) / 60.
+ + (seconds as f64) / 3_600.
+ + (nanoseconds as f64) / 3_600. / 1_000_000_000.;
+ format_float(output, hours, 2, decimal_digits)?;
+ }
+ TimePrecision::Minute { decimal_digits } => {
+ bytes += format_number_pad_zero::<2, _, _>(output, hours)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
+ let minutes = (minutes as f64)
+ + (seconds as f64) / 60.
+ + (nanoseconds as f64) / 60. / 1_000_000_000.;
+ bytes += format_float(output, minutes, 2, decimal_digits)?;
+ }
+ TimePrecision::Second { decimal_digits } => {
+ bytes += format_number_pad_zero::<2, _, _>(output, hours)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, minutes)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
+ let seconds = (seconds as f64) + (nanoseconds as f64) / 1_000_000_000.;
+ bytes += format_float(output, seconds, 2, decimal_digits)?;
+ }
+ }
+
+ Ok(bytes)
+}
+
+/// Format the UTC offset portion of ISO 8601.
+pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>(
+ output: &mut W,
+ offset: UtcOffset,
+) -> Result<usize, error::Format> {
+ if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() {
+ return Ok(write(output, b"Z")?);
+ }
+
+ let mut bytes = 0;
+
+ let (hours, minutes, seconds) = offset.as_hms();
+ if seconds != 0 {
+ return Err(error::Format::InvalidComponent("offset_second"));
+ }
+ bytes += write_if_else(output, offset.is_negative(), b"-", b"+")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, hours.unsigned_abs())?;
+
+ if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 {
+ return Err(error::Format::InvalidComponent("offset_minute"));
+ } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute {
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, minutes.unsigned_abs())?;
+ }
+
+ Ok(bytes)
+}
diff --git a/third_party/rust/time/src/formatting/mod.rs b/third_party/rust/time/src/formatting/mod.rs
new file mode 100644
index 0000000000..e5409cc58f
--- /dev/null
+++ b/third_party/rust/time/src/formatting/mod.rs
@@ -0,0 +1,506 @@
+//! Formatting for various types.
+
+pub(crate) mod formattable;
+mod iso8601;
+
+use core::num::NonZeroU8;
+use std::io;
+
+pub use self::formattable::Formattable;
+use crate::format_description::{modifier, Component};
+use crate::{error, Date, Time, UtcOffset};
+
+#[allow(clippy::missing_docs_in_private_items)]
+const MONTH_NAMES: [&[u8]; 12] = [
+ b"January",
+ b"February",
+ b"March",
+ b"April",
+ b"May",
+ b"June",
+ b"July",
+ b"August",
+ b"September",
+ b"October",
+ b"November",
+ b"December",
+];
+
+#[allow(clippy::missing_docs_in_private_items)]
+const WEEKDAY_NAMES: [&[u8]; 7] = [
+ b"Monday",
+ b"Tuesday",
+ b"Wednesday",
+ b"Thursday",
+ b"Friday",
+ b"Saturday",
+ b"Sunday",
+];
+
+// region: extension trait
+/// A trait that indicates the formatted width of the value can be determined.
+///
+/// Note that this should not be implemented for any signed integers. This forces the caller to
+/// write the sign if desired.
+pub(crate) trait DigitCount {
+ /// The number of digits in the stringified value.
+ fn num_digits(self) -> u8;
+}
+impl DigitCount for u8 {
+ fn num_digits(self) -> u8 {
+ // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
+ if self < 10 {
+ 1
+ } else if self < 100 {
+ 2
+ } else {
+ 3
+ }
+ }
+}
+impl DigitCount for u16 {
+ fn num_digits(self) -> u8 {
+ // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
+ if self < 10 {
+ 1
+ } else if self < 100 {
+ 2
+ } else if self < 1_000 {
+ 3
+ } else if self < 10_000 {
+ 4
+ } else {
+ 5
+ }
+ }
+}
+
+impl DigitCount for u32 {
+ fn num_digits(self) -> u8 {
+ /// Lookup table
+ const TABLE: &[u64] = &[
+ 0x0001_0000_0000,
+ 0x0001_0000_0000,
+ 0x0001_0000_0000,
+ 0x0001_FFFF_FFF6,
+ 0x0002_0000_0000,
+ 0x0002_0000_0000,
+ 0x0002_FFFF_FF9C,
+ 0x0003_0000_0000,
+ 0x0003_0000_0000,
+ 0x0003_FFFF_FC18,
+ 0x0004_0000_0000,
+ 0x0004_0000_0000,
+ 0x0004_0000_0000,
+ 0x0004_FFFF_D8F0,
+ 0x0005_0000_0000,
+ 0x0005_0000_0000,
+ 0x0005_FFFE_7960,
+ 0x0006_0000_0000,
+ 0x0006_0000_0000,
+ 0x0006_FFF0_BDC0,
+ 0x0007_0000_0000,
+ 0x0007_0000_0000,
+ 0x0007_0000_0000,
+ 0x0007_FF67_6980,
+ 0x0008_0000_0000,
+ 0x0008_0000_0000,
+ 0x0008_FA0A_1F00,
+ 0x0009_0000_0000,
+ 0x0009_0000_0000,
+ 0x0009_C465_3600,
+ 0x000A_0000_0000,
+ 0x000A_0000_0000,
+ ];
+ ((self as u64 + TABLE[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _
+ }
+}
+// endregion extension trait
+
+/// Write all bytes to the output, returning the number of bytes written.
+pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
+ output.write_all(bytes)?;
+ Ok(bytes.len())
+}
+
+/// If `pred` is true, write all bytes to the output, returning the number of bytes written.
+pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> {
+ if pred { write(output, bytes) } else { Ok(0) }
+}
+
+/// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`.
+pub(crate) fn write_if_else(
+ output: &mut impl io::Write,
+ pred: bool,
+ true_bytes: &[u8],
+ false_bytes: &[u8],
+) -> io::Result<usize> {
+ write(output, if pred { true_bytes } else { false_bytes })
+}
+
+/// 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.
+pub(crate) fn format_float(
+ output: &mut impl io::Write,
+ value: f64,
+ digits_before_decimal: u8,
+ digits_after_decimal: Option<NonZeroU8>,
+) -> io::Result<usize> {
+ match digits_after_decimal {
+ Some(digits_after_decimal) => {
+ let digits_after_decimal = digits_after_decimal.get() as usize;
+ let width = digits_before_decimal as usize + 1 + digits_after_decimal;
+ write!(
+ output,
+ "{value:0>width$.digits_after_decimal$}",
+ value = value,
+ width = width,
+ digits_after_decimal = digits_after_decimal,
+ )?;
+ Ok(width)
+ }
+ None => {
+ let value = value.trunc() as u64;
+ let width = digits_before_decimal as usize;
+ write!(output, "{value:0>width$?}", value = value, width = width)?;
+ Ok(width)
+ }
+ }
+}
+
+/// Format a number with the provided padding and width.
+///
+/// The sign must be written by the caller.
+pub(crate) fn format_number<const WIDTH: u8, W: io::Write, V: itoa::Integer + DigitCount + Copy>(
+ output: &mut W,
+ value: V,
+ padding: modifier::Padding,
+) -> Result<usize, io::Error> {
+ match padding {
+ modifier::Padding::Space => format_number_pad_space::<WIDTH, _, _>(output, value),
+ modifier::Padding::Zero => format_number_pad_zero::<WIDTH, _, _>(output, value),
+ modifier::Padding::None => write(output, itoa::Buffer::new().format(value).as_bytes()),
+ }
+}
+
+/// Format a number with the provided width and spaces as padding.
+///
+/// The sign must be written by the caller.
+pub(crate) fn format_number_pad_space<
+ const WIDTH: u8,
+ W: io::Write,
+ V: itoa::Integer + DigitCount + Copy,
+>(
+ output: &mut W,
+ value: V,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
+ bytes += write(output, b" ")?;
+ }
+ bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
+ Ok(bytes)
+}
+
+/// Format a number with the provided width and zeros as padding.
+///
+/// The sign must be written by the caller.
+pub(crate) fn format_number_pad_zero<
+ const WIDTH: u8,
+ W: io::Write,
+ V: itoa::Integer + DigitCount + Copy,
+>(
+ output: &mut W,
+ value: V,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
+ bytes += write(output, b"0")?;
+ }
+ bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
+ Ok(bytes)
+}
+
+/// Format the provided component into the designated output. An `Err` will be returned if the
+/// component requires information that it does not provide or if the value cannot be output to the
+/// stream.
+pub(crate) fn format_component(
+ output: &mut impl io::Write,
+ component: Component,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+) -> Result<usize, error::Format> {
+ use Component::*;
+ Ok(match (component, date, time, offset) {
+ (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
+ (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
+ (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
+ (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
+ (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
+ (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
+ (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
+ (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
+ (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
+ (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
+ (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
+ (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
+ (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
+ (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
+ _ => return Err(error::Format::InsufficientTypeInformation),
+ })
+}
+
+// region: date formatters
+/// Format the day into the designated output.
+fn fmt_day(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Day { padding }: modifier::Day,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, date.day(), padding)
+}
+
+/// Format the month into the designated output.
+fn fmt_month(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Month {
+ padding,
+ repr,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Month,
+) -> Result<usize, io::Error> {
+ match repr {
+ modifier::MonthRepr::Numerical => {
+ format_number::<2, _, _>(output, date.month() as u8, padding)
+ }
+ modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]),
+ modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]),
+ }
+}
+
+/// Format the ordinal into the designated output.
+fn fmt_ordinal(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Ordinal { padding }: modifier::Ordinal,
+) -> Result<usize, io::Error> {
+ format_number::<3, _, _>(output, date.ordinal(), padding)
+}
+
+/// Format the weekday into the designated output.
+fn fmt_weekday(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Weekday {
+ repr,
+ one_indexed,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Weekday,
+) -> Result<usize, io::Error> {
+ match repr {
+ modifier::WeekdayRepr::Short => write(
+ output,
+ &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
+ ),
+ modifier::WeekdayRepr::Long => write(
+ output,
+ WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize],
+ ),
+ modifier::WeekdayRepr::Sunday => format_number::<1, _, _>(
+ output,
+ date.weekday().number_days_from_sunday() + one_indexed as u8,
+ modifier::Padding::None,
+ ),
+ modifier::WeekdayRepr::Monday => format_number::<1, _, _>(
+ output,
+ date.weekday().number_days_from_monday() + one_indexed as u8,
+ modifier::Padding::None,
+ ),
+ }
+}
+
+/// Format the week number into the designated output.
+fn fmt_week_number(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(
+ output,
+ match repr {
+ modifier::WeekNumberRepr::Iso => date.iso_week(),
+ modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
+ modifier::WeekNumberRepr::Monday => date.monday_based_week(),
+ },
+ padding,
+ )
+}
+
+/// Format the year into the designated output.
+fn fmt_year(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Year {
+ padding,
+ repr,
+ iso_week_based,
+ sign_is_mandatory,
+ }: modifier::Year,
+) -> Result<usize, io::Error> {
+ let full_year = if iso_week_based {
+ date.iso_year_week().0
+ } else {
+ date.year()
+ };
+ let value = match repr {
+ modifier::YearRepr::Full => full_year,
+ modifier::YearRepr::LastTwo => (full_year % 100).abs(),
+ };
+ let format_number = match repr {
+ #[cfg(feature = "large-dates")]
+ modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6, _, _>,
+ #[cfg(feature = "large-dates")]
+ modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5, _, _>,
+ modifier::YearRepr::Full => format_number::<4, _, _>,
+ modifier::YearRepr::LastTwo => format_number::<2, _, _>,
+ };
+ let mut bytes = 0;
+ if repr != modifier::YearRepr::LastTwo {
+ if full_year < 0 {
+ bytes += write(output, b"-")?;
+ } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
+ bytes += write(output, b"+")?;
+ }
+ }
+ bytes += format_number(output, value.unsigned_abs(), padding)?;
+ Ok(bytes)
+}
+// endregion date formatters
+
+// region: time formatters
+/// Format the hour into the designated output.
+fn fmt_hour(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Hour {
+ padding,
+ is_12_hour_clock,
+ }: modifier::Hour,
+) -> Result<usize, io::Error> {
+ let value = match (time.hour(), is_12_hour_clock) {
+ (hour, false) => hour,
+ (0 | 12, true) => 12,
+ (hour, true) if hour < 12 => hour,
+ (hour, true) => hour - 12,
+ };
+ format_number::<2, _, _>(output, value, padding)
+}
+
+/// Format the minute into the designated output.
+fn fmt_minute(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Minute { padding }: modifier::Minute,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, time.minute(), padding)
+}
+
+/// Format the period into the designated output.
+fn fmt_period(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Period {
+ is_uppercase,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Period,
+) -> Result<usize, io::Error> {
+ match (time.hour() >= 12, is_uppercase) {
+ (false, false) => write(output, b"am"),
+ (false, true) => write(output, b"AM"),
+ (true, false) => write(output, b"pm"),
+ (true, true) => write(output, b"PM"),
+ }
+}
+
+/// Format the second into the designated output.
+fn fmt_second(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Second { padding }: modifier::Second,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, time.second(), padding)
+}
+
+/// Format the subsecond into the designated output.
+fn fmt_subsecond<W: io::Write>(
+ output: &mut W,
+ time: Time,
+ modifier::Subsecond { digits }: modifier::Subsecond,
+) -> Result<usize, io::Error> {
+ use modifier::SubsecondDigits::*;
+ let nanos = time.nanosecond();
+
+ if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
+ format_number_pad_zero::<9, _, _>(output, nanos)
+ } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) {
+ format_number_pad_zero::<8, _, _>(output, nanos / 10)
+ } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
+ format_number_pad_zero::<7, _, _>(output, nanos / 100)
+ } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) {
+ format_number_pad_zero::<6, _, _>(output, nanos / 1_000)
+ } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
+ format_number_pad_zero::<5, _, _>(output, nanos / 10_000)
+ } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) {
+ format_number_pad_zero::<4, _, _>(output, nanos / 100_000)
+ } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
+ format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000)
+ } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) {
+ format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
+ } else {
+ format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
+ }
+}
+// endregion time formatters
+
+// region: offset formatters
+/// Format the offset hour into the designated output.
+fn fmt_offset_hour(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetHour {
+ padding,
+ sign_is_mandatory,
+ }: modifier::OffsetHour,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ if offset.is_negative() {
+ bytes += write(output, b"-")?;
+ } else if sign_is_mandatory {
+ bytes += write(output, b"+")?;
+ }
+ bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?;
+ Ok(bytes)
+}
+
+/// Format the offset minute into the designated output.
+fn fmt_offset_minute(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetMinute { padding }: modifier::OffsetMinute,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding)
+}
+
+/// Format the offset second into the designated output.
+fn fmt_offset_second(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetSecond { padding }: modifier::OffsetSecond,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding)
+}
+// endregion offset formatters
diff --git a/third_party/rust/time/src/instant.rs b/third_party/rust/time/src/instant.rs
new file mode 100644
index 0000000000..2d2f65eefb
--- /dev/null
+++ b/third_party/rust/time/src/instant.rs
@@ -0,0 +1,262 @@
+//! The [`Instant`] struct and its associated `impl`s.
+
+use core::borrow::Borrow;
+use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+use std::time::Instant as StdInstant;
+
+use crate::Duration;
+
+/// A measurement of a monotonically non-decreasing clock. Opaque and useful only with [`Duration`].
+///
+/// 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 [`Duration`]s, but is otherwise identical
+/// to [`std::time::Instant`].
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Instant(pub StdInstant);
+
+impl Instant {
+ // region: delegation
+ /// Returns an `Instant` corresponding to "now".
+ ///
+ /// ```rust
+ /// # use time::Instant;
+ /// println!("{:?}", Instant::now());
+ /// ```
+ 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
+ /// # use time::{Instant, ext::{NumericalStdDuration, NumericalDuration}};
+ /// # use std::thread;
+ /// let instant = Instant::now();
+ /// thread::sleep(1.std_milliseconds());
+ /// assert!(instant.elapsed() >= 1.milliseconds());
+ /// ```
+ pub fn elapsed(self) -> Duration {
+ Self::now() - self
+ }
+ // endregion delegation
+
+ // region: checked arithmetic
+ /// 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
+ /// # 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()));
+ /// ```
+ pub fn checked_add(self, duration: Duration) -> 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
+ /// # 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()));
+ /// ```
+ pub fn checked_sub(self, duration: Duration) -> 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)
+ }
+ }
+ // endregion checked arithmetic
+
+ /// Obtain the inner [`std::time::Instant`].
+ ///
+ /// ```rust
+ /// # use time::Instant;
+ /// let now = Instant::now();
+ /// assert_eq!(now.into_inner(), now.0);
+ /// ```
+ pub const fn into_inner(self) -> StdInstant {
+ self.0
+ }
+}
+
+// region: trait impls
+impl From<StdInstant> for Instant {
+ fn from(instant: StdInstant) -> Self {
+ Self(instant)
+ }
+}
+
+impl From<Instant> for StdInstant {
+ fn from(instant: Instant) -> Self {
+ instant.0
+ }
+}
+
+impl Sub for Instant {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Self::Output {
+ match self.0.cmp(&other.0) {
+ Ordering::Equal => Duration::ZERO,
+ Ordering::Greater => (self.0 - other.0)
+ .try_into()
+ .expect("overflow converting `std::time::Duration` to `time::Duration`"),
+ Ordering::Less => -Duration::try_from(other.0 - self.0)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`"),
+ }
+ }
+}
+
+impl Sub<StdInstant> for Instant {
+ type Output = Duration;
+
+ fn sub(self, other: StdInstant) -> Self::Output {
+ self - Self(other)
+ }
+}
+
+impl Sub<Instant> for StdInstant {
+ type Output = Duration;
+
+ fn sub(self, other: Instant) -> Self::Output {
+ Instant(self) - other
+ }
+}
+
+impl Add<Duration> for Instant {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ 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 Add<Duration> for StdInstant {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ (Instant(self) + duration).0
+ }
+}
+
+impl Add<StdDuration> for Instant {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ Self(self.0 + duration)
+ }
+}
+
+impl_add_assign!(Instant: Duration, StdDuration);
+impl_add_assign!(StdInstant: Duration);
+
+impl Sub<Duration> for Instant {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ 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<Duration> for StdInstant {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ (Instant(self) - duration).0
+ }
+}
+
+impl Sub<StdDuration> for Instant {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ Self(self.0 - duration)
+ }
+}
+
+impl_sub_assign!(Instant: Duration, StdDuration);
+impl_sub_assign!(StdInstant: Duration);
+
+impl PartialEq<StdInstant> for Instant {
+ fn eq(&self, rhs: &StdInstant) -> bool {
+ self.0.eq(rhs)
+ }
+}
+
+impl PartialEq<Instant> for StdInstant {
+ fn eq(&self, rhs: &Instant) -> bool {
+ self.eq(&rhs.0)
+ }
+}
+
+impl PartialOrd<StdInstant> for Instant {
+ fn partial_cmp(&self, rhs: &StdInstant) -> Option<Ordering> {
+ self.0.partial_cmp(rhs)
+ }
+}
+
+impl PartialOrd<Instant> for StdInstant {
+ fn partial_cmp(&self, rhs: &Instant) -> Option<Ordering> {
+ self.partial_cmp(&rhs.0)
+ }
+}
+
+impl AsRef<StdInstant> for Instant {
+ fn as_ref(&self) -> &StdInstant {
+ &self.0
+ }
+}
+
+impl Borrow<StdInstant> for Instant {
+ fn borrow(&self) -> &StdInstant {
+ &self.0
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/lib.rs b/third_party/rust/time/src/lib.rs
new file mode 100644
index 0000000000..b9868c1788
--- /dev/null
+++ b/third_party/rust/time/src/lib.rs
@@ -0,0 +1,357 @@
+//! # 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 except [`Instant`].
+//!
+//! - `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.
+//!
+//! - `serde-well-known` (_implicitly enables `serde-human-readable`_)
+//!
+//! _This feature flag is deprecated and will be removed in a future breaking release. Use the
+//! `serde-human-readable` feature instead._
+//!
+//! Enables support for serializing and deserializing well-known formats using serde's
+//! [`#[with]` attribute](https://serde.rs/field-attrs.html#with).
+//!
+//! - `rand`
+//!
+//! Enables [rand](https://docs.rs/rand) support for all types.
+//!
+//! - `quickcheck` (_implicitly enables `alloc`_)
+//!
+//! Enables [quickcheck](https://docs.rs/quickcheck) support for all types except [`Instant`].
+//!
+//! - `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.
+//!
+//! <small>
+//! One feature only available to end users is the <code>unsound_local_offset</code> cfg. This
+//! enables obtaining the system's UTC offset even when it is unsound. To enable this, use the
+//! <code>RUSTFLAGS</code> environment variable. This is untested and officially unsupported. Do not
+//! use this unless you understand the risk.
+//! </small>
+
+#![doc(html_playground_url = "https://play.rust-lang.org")]
+#![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))]
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(
+ anonymous_parameters,
+ clippy::all,
+ clippy::alloc_instead_of_core,
+ clippy::explicit_auto_deref,
+ clippy::obfuscated_if_else,
+ clippy::std_instead_of_core,
+ clippy::undocumented_unsafe_blocks,
+ const_err,
+ illegal_floating_point_literal_pattern,
+ late_bound_lifetime_arguments,
+ path_statements,
+ patterns_in_fns_without_body,
+ rust_2018_idioms,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unsafe_op_in_unsafe_fn,
+ unused_extern_crates,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::private_intra_doc_links
+)]
+#![warn(
+ clippy::dbg_macro,
+ clippy::decimal_literal_representation,
+ clippy::get_unwrap,
+ clippy::missing_docs_in_private_items,
+ clippy::nursery,
+ clippy::print_stdout,
+ clippy::todo,
+ clippy::unimplemented,
+ clippy::unnested_or_patterns,
+ clippy::unwrap_in_result,
+ clippy::unwrap_used,
+ clippy::use_debug,
+ deprecated_in_future,
+ missing_copy_implementations,
+ missing_debug_implementations,
+ unused_qualifications,
+ variant_size_differences
+)]
+#![allow(
+ clippy::redundant_pub_crate, // suggests bad style
+ clippy::option_if_let_else, // suggests terrible code
+ clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released
+ clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released
+)]
+#![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;
+
+// region: macros
+/// Helper macro for easily implementing `OpAssign`.
+macro_rules! __impl_assign {
+ ($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$(
+ #[allow(unused_qualifications)]
+ $(#[$attr])*
+ impl core::ops::$op<$t> for $target {
+ fn $fn(&mut self, rhs: $t) {
+ *self = *self $sym rhs;
+ }
+ }
+ )+};
+}
+
+/// Implement `AddAssign` for the provided types.
+macro_rules! impl_add_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(+ AddAssign add_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `SubAssign` for the provided types.
+macro_rules! impl_sub_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(- SubAssign sub_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `MulAssign` for the provided types.
+macro_rules! impl_mul_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(* MulAssign mul_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `DivAssign` for the provided types.
+macro_rules! impl_div_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(/ DivAssign div_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Division of integers, rounding the resulting value towards negative infinity.
+macro_rules! div_floor {
+ ($a:expr, $b:expr) => {{
+ let _a = $a;
+ let _b = $b;
+
+ let (_quotient, _remainder) = (_a / _b, _a % _b);
+
+ if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) {
+ _quotient - 1
+ } else {
+ _quotient
+ }
+ }};
+}
+
+/// Cascade an out-of-bounds value.
+macro_rules! cascade {
+ (@ordinal ordinal) => {};
+ (@year year) => {};
+
+ // Cascade an out-of-bounds value from "from" to "to".
+ ($from:ident in $min:literal.. $max:literal => $to:tt) => {
+ #[allow(unused_comparisons, unused_assignments)]
+ if $from >= $max {
+ $from -= $max - $min;
+ $to += 1;
+ } else if $from < $min {
+ $from += $max - $min;
+ $to -= 1;
+ }
+ };
+
+ // Special case the ordinal-to-year cascade, as it has different behavior.
+ ($ordinal:ident => $year:ident) => {
+ // We need to actually capture the idents. Without this, macro hygiene causes errors.
+ cascade!(@ordinal $ordinal);
+ cascade!(@year $year);
+ #[allow(unused_assignments)]
+ if $ordinal > crate::util::days_in_year($year) as i16 {
+ $ordinal -= crate::util::days_in_year($year) as i16;
+ $year += 1;
+ } else if $ordinal < 1 {
+ $year -= 1;
+ $ordinal += crate::util::days_in_year($year) as i16;
+ }
+ };
+}
+
+/// Returns `Err(error::ComponentRange)` if the value is not in range.
+macro_rules! ensure_value_in_range {
+ ($value:ident in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: false,
+ });
+ }
+ }};
+
+ ($value:ident conditionally in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: true,
+ });
+ }
+ }};
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is
+/// usable in `const` contexts.
+macro_rules! const_try {
+ ($e:expr) => {
+ match $e {
+ Ok(value) => value,
+ Err(error) => return Err(error),
+ }
+ };
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but is usable in `const` contexts.
+macro_rules! const_try_opt {
+ ($e:expr) => {
+ match $e {
+ Some(value) => value,
+ None => return None,
+ }
+ };
+}
+
+/// Try to unwrap an expression, panicking if not possible.
+///
+/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
+macro_rules! expect_opt {
+ ($e:expr, $message:literal) => {
+ match $e {
+ Some(value) => value,
+ None => crate::expect_failed($message),
+ }
+ };
+}
+// endregion macros
+
+mod date;
+mod duration;
+pub mod error;
+pub mod ext;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod format_description;
+#[cfg(feature = "formatting")]
+pub mod formatting;
+#[cfg(feature = "std")]
+mod instant;
+#[cfg(feature = "macros")]
+pub mod macros;
+mod month;
+mod offset_date_time;
+#[cfg(feature = "parsing")]
+pub mod parsing;
+mod primitive_date_time;
+#[cfg(feature = "quickcheck")]
+mod quickcheck;
+#[cfg(feature = "rand")]
+mod rand;
+#[cfg(feature = "serde")]
+#[allow(missing_copy_implementations, missing_debug_implementations)]
+pub mod serde;
+mod sys;
+#[cfg(test)]
+mod tests;
+mod time;
+mod utc_offset;
+pub mod util;
+mod weekday;
+
+pub use crate::date::Date;
+pub use crate::duration::Duration;
+pub use crate::error::Error;
+#[cfg(feature = "std")]
+pub use crate::instant::Instant;
+pub use crate::month::Month;
+pub use crate::offset_date_time::OffsetDateTime;
+pub use crate::primitive_date_time::PrimitiveDateTime;
+pub use crate::time::Time;
+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>;
+
+/// This is a separate function to reduce the code size of `expect_opt!`.
+#[inline(never)]
+#[cold]
+#[track_caller]
+const fn expect_failed(message: &str) -> ! {
+ panic!("{}", message)
+}
diff --git a/third_party/rust/time/src/macros.rs b/third_party/rust/time/src/macros.rs
new file mode 100644
index 0000000000..4f295e2ed8
--- /dev/null
+++ b/third_party/rust/time/src/macros.rs
@@ -0,0 +1,132 @@
+//! 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 - W 01 - 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 [`PrimitiveDateTime`] 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 [`PrimitiveDateTime`].
+///
+/// [`OffsetDateTime`]: crate::OffsetDateTime
+/// [`PrimitiveDateTime`]: crate::PrimitiveDateTime
+///
+/// ```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`],
+/// such that it can be used in `#![no_alloc]` situations.
+///
+/// The resulting expression can be used in `const` or `static` declarations, and implements
+/// the sealed traits required for both formatting and parsing.
+#[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("[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;
diff --git a/third_party/rust/time/src/month.rs b/third_party/rust/time/src/month.rs
new file mode 100644
index 0000000000..0c657e9821
--- /dev/null
+++ b/third_party/rust/time/src/month.rs
@@ -0,0 +1,164 @@
+//! The `Month` enum and its associated `impl`s.
+
+use core::fmt;
+use core::num::NonZeroU8;
+use core::str::FromStr;
+
+use self::Month::*;
+use crate::error;
+
+/// Months of the year.
+#[allow(clippy::missing_docs_in_private_items)] // variants
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Month {
+ January = 1,
+ February = 2,
+ March = 3,
+ April = 4,
+ May = 5,
+ June = 6,
+ July = 7,
+ August = 8,
+ September = 9,
+ October = 10,
+ November = 11,
+ December = 12,
+}
+
+impl Month {
+ /// Create a `Month` from its numerical value.
+ pub(crate) const fn from_number(n: NonZeroU8) -> 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),
+ n => Err(error::ComponentRange {
+ name: "month",
+ minimum: 1,
+ maximum: 12,
+ value: n as _,
+ conditional_range: false,
+ }),
+ }
+ }
+
+ /// Get the previous month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// assert_eq!(Month::January.previous(), Month::December);
+ /// ```
+ 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);
+ /// ```
+ 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,
+ }
+ }
+}
+
+impl fmt::Display for Month {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(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 FromStr for Month {
+ type Err = error::InvalidVariant;
+
+ 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 {
+ fn from(month: Month) -> Self {
+ month as _
+ }
+}
+
+impl TryFrom<u8> for Month {
+ type Error = error::ComponentRange;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match NonZeroU8::new(value) {
+ Some(value) => Self::from_number(value),
+ None => Err(error::ComponentRange {
+ name: "month",
+ minimum: 1,
+ maximum: 12,
+ value: 0,
+ conditional_range: false,
+ }),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/offset_date_time.rs b/third_party/rust/time/src/offset_date_time.rs
new file mode 100644
index 0000000000..c39a2b1d05
--- /dev/null
+++ b/third_party/rust/time/src/offset_date_time.rs
@@ -0,0 +1,1398 @@
+//! The [`OffsetDateTime`] struct and its associated `impl`s.
+
+use core::cmp::Ordering;
+#[cfg(feature = "std")]
+use core::convert::From;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+#[cfg(feature = "std")]
+use std::time::SystemTime;
+
+use crate::date::{MAX_YEAR, MIN_YEAR};
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+#[cfg(feature = "parsing")]
+use crate::util;
+use crate::{error, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// The Julian day of the Unix epoch.
+const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day();
+
+/// A [`PrimitiveDateTime`] with a [`UtcOffset`].
+///
+/// All comparisons are performed using the UTC time.
+#[derive(Clone, Copy, Eq)]
+pub struct OffsetDateTime {
+ /// The [`PrimitiveDateTime`], which is _always_ in the stored offset.
+ pub(crate) local_datetime: PrimitiveDateTime,
+ /// The [`UtcOffset`], which will be added to the [`PrimitiveDateTime`] as necessary.
+ pub(crate) offset: UtcOffset,
+}
+
+impl OffsetDateTime {
+ /// Midnight, 1 January, 1970 (UTC).
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::datetime;
+ /// assert_eq!(OffsetDateTime::UNIX_EPOCH, datetime!(1970-01-01 0:00 UTC),);
+ /// ```
+ pub const UNIX_EPOCH: Self = Date::__from_ordinal_date_unchecked(1970, 1)
+ .midnight()
+ .assume_utc();
+
+ // region: now
+ /// Create a new `OffsetDateTime` with the current date and time in UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::offset;
+ /// assert!(OffsetDateTime::now_utc().year() >= 2019);
+ /// assert_eq!(OffsetDateTime::now_utc().offset(), offset!(UTC));
+ /// ```
+ #[cfg(feature = "std")]
+ pub fn now_utc() -> Self {
+ #[cfg(all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ ))]
+ {
+ js_sys::Date::new_0().into()
+ }
+
+ #[cfg(not(all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ )))]
+ SystemTime::now().into()
+ }
+
+ /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset.
+ /// If the offset cannot be determined, an error is returned.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # if false {
+ /// assert!(OffsetDateTime::now_local().is_ok());
+ /// # }
+ /// ```
+ #[cfg(feature = "local-offset")]
+ pub fn now_local() -> Result<Self, error::IndeterminateOffset> {
+ let t = Self::now_utc();
+ Ok(t.to_offset(UtcOffset::local_offset_at(t)?))
+ }
+ // endregion now
+
+ /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2000-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .year(),
+ /// 1999,
+ /// );
+ ///
+ /// // Let's see what time Sydney's new year's celebration is in New York and Los Angeles.
+ ///
+ /// // Construct midnight on new year's in Sydney.
+ /// let sydney = datetime!(2000-01-01 0:00 +11);
+ /// let new_york = sydney.to_offset(offset!(-5));
+ /// let los_angeles = sydney.to_offset(offset!(-8));
+ /// assert_eq!(sydney.hour(), 0);
+ /// assert_eq!(new_york.hour(), 8);
+ /// assert_eq!(los_angeles.hour(), 5);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// This method panics if the local date-time in the new offset is outside the supported range.
+ pub const fn to_offset(self, offset: UtcOffset) -> Self {
+ if self.offset.whole_hours() == offset.whole_hours()
+ && self.offset.minutes_past_hour() == offset.minutes_past_hour()
+ && self.offset.seconds_past_minute() == offset.seconds_past_minute()
+ {
+ return self;
+ }
+
+ let (year, ordinal, time) = self.to_offset_raw(offset);
+
+ if year > MAX_YEAR || year < MIN_YEAR {
+ panic!("local datetime out of valid range");
+ }
+
+ Date::__from_ordinal_date_unchecked(year, ordinal)
+ .with_time(time)
+ .assume_offset(offset)
+ }
+
+ /// Equivalent to `.to_offset(UtcOffset::UTC)`, but returning the year, ordinal, and time. This
+ /// avoids constructing an invalid [`Date`] if the new value is out of range.
+ const fn to_offset_raw(self, offset: UtcOffset) -> (i32, u16, Time) {
+ let from = self.offset;
+ let to = offset;
+
+ // Fast path for when no conversion is necessary.
+ if from.whole_hours() == to.whole_hours()
+ && from.minutes_past_hour() == to.minutes_past_hour()
+ && from.seconds_past_minute() == to.seconds_past_minute()
+ {
+ return (self.year(), self.ordinal(), self.time());
+ }
+
+ let mut second = self.second() as i16 - from.seconds_past_minute() as i16
+ + to.seconds_past_minute() as i16;
+ let mut minute =
+ self.minute() as i16 - from.minutes_past_hour() as i16 + to.minutes_past_hour() as i16;
+ let mut hour = self.hour() as i8 - from.whole_hours() + to.whole_hours();
+ let (mut year, ordinal) = self.to_ordinal_date();
+ let mut ordinal = ordinal as i16;
+
+ // Cascade the values twice. This is needed because the values are adjusted twice above.
+ cascade!(second in 0..60 => minute);
+ cascade!(second in 0..60 => minute);
+ cascade!(minute in 0..60 => hour);
+ cascade!(minute in 0..60 => hour);
+ cascade!(hour in 0..24 => ordinal);
+ cascade!(hour in 0..24 => ordinal);
+ cascade!(ordinal => year);
+
+ debug_assert!(ordinal > 0);
+ debug_assert!(ordinal <= crate::util::days_in_year(year) as i16);
+
+ (
+ year,
+ ordinal as _,
+ Time::__from_hms_nanos_unchecked(
+ hour as _,
+ minute as _,
+ second as _,
+ self.nanosecond(),
+ ),
+ )
+ }
+
+ // region: constructors
+ /// Create an `OffsetDateTime` from the provided Unix timestamp. Calling `.offset()` on the
+ /// resulting value is guaranteed to return UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(0),
+ /// Ok(OffsetDateTime::UNIX_EPOCH),
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(1_546_300_800),
+ /// Ok(datetime!(2019-01-01 0:00 UTC)),
+ /// );
+ /// ```
+ ///
+ /// If you have a timestamp-nanosecond pair, you can use something along the lines of the
+ /// following:
+ ///
+ /// ```rust
+ /// # use time::{Duration, OffsetDateTime, ext::NumericalDuration};
+ /// let (timestamp, nanos) = (1, 500_000_000);
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(timestamp)? + Duration::nanoseconds(nanos),
+ /// OffsetDateTime::UNIX_EPOCH + 1.5.seconds()
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange> {
+ #[allow(clippy::missing_docs_in_private_items)]
+ const MIN_TIMESTAMP: i64 = Date::MIN.midnight().assume_utc().unix_timestamp();
+ #[allow(clippy::missing_docs_in_private_items)]
+ const MAX_TIMESTAMP: i64 = Date::MAX
+ .with_time(Time::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999))
+ .assume_utc()
+ .unix_timestamp();
+
+ ensure_value_in_range!(timestamp in MIN_TIMESTAMP => MAX_TIMESTAMP);
+
+ // Use the unchecked method here, as the input validity has already been verified.
+ let date = Date::from_julian_day_unchecked(
+ UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, 86_400) as i32,
+ );
+
+ let seconds_within_day = timestamp.rem_euclid(86_400);
+ let time = Time::__from_hms_nanos_unchecked(
+ (seconds_within_day / 3_600) as _,
+ ((seconds_within_day % 3_600) / 60) as _,
+ (seconds_within_day % 60) as _,
+ 0,
+ );
+
+ Ok(PrimitiveDateTime::new(date, time).assume_utc())
+ }
+
+ /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling
+ /// `.offset()` on the resulting value is guaranteed to return UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp_nanos(0),
+ /// Ok(OffsetDateTime::UNIX_EPOCH),
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000),
+ /// Ok(datetime!(2019-01-01 0:00 UTC)),
+ /// );
+ /// ```
+ pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange> {
+ let datetime = const_try!(Self::from_unix_timestamp(
+ div_floor!(timestamp, 1_000_000_000) as i64
+ ));
+
+ Ok(datetime
+ .local_datetime
+ .replace_time(Time::__from_hms_nanos_unchecked(
+ datetime.local_datetime.hour(),
+ datetime.local_datetime.minute(),
+ datetime.local_datetime.second(),
+ timestamp.rem_euclid(1_000_000_000) as u32,
+ ))
+ .assume_utc())
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the [`UtcOffset`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).offset(), offset!(UTC));
+ /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1));
+ /// ```
+ pub const fn offset(self) -> UtcOffset {
+ self.offset
+ }
+
+ /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp(), 0);
+ /// assert_eq!(datetime!(1970-01-01 0:00 -1).unix_timestamp(), 3_600);
+ /// ```
+ pub const fn unix_timestamp(self) -> i64 {
+ let offset = self.offset.whole_seconds() as i64;
+
+ let days =
+ (self.local_datetime.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * 86_400;
+ let hours = self.local_datetime.hour() as i64 * 3_600;
+ let minutes = self.local_datetime.minute() as i64 * 60;
+ let seconds = self.local_datetime.second() as i64;
+ days + hours + minutes + seconds - offset
+ }
+
+ /// Get the Unix timestamp in nanoseconds.
+ ///
+ /// ```rust
+ /// use time_macros::datetime;
+ /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos(), 0);
+ /// assert_eq!(
+ /// datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos(),
+ /// 3_600_000_000_000,
+ /// );
+ /// ```
+ pub const fn unix_timestamp_nanos(self) -> i128 {
+ self.unix_timestamp() as i128 * 1_000_000_000 + self.nanosecond() as i128
+ }
+
+ /// Get the [`Date`] in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).date(), date!(2019-01-01));
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .date(),
+ /// date!(2018-12-31),
+ /// );
+ /// ```
+ pub const fn date(self) -> Date {
+ self.local_datetime.date()
+ }
+
+ /// Get the [`Time`] in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset, time};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).time(), time!(0:00));
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .time(),
+ /// time!(23:00)
+ /// );
+ /// ```
+ pub const fn time(self) -> Time {
+ self.local_datetime.time()
+ }
+
+ // region: date getters
+ /// Get the year of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).year(), 2019);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .year(),
+ /// 2020,
+ /// );
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.date().year()
+ }
+
+ /// Get the month of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).month(), Month::January);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .month(),
+ /// Month::January,
+ /// );
+ /// ```
+ pub const fn month(self) -> Month {
+ self.date().month()
+ }
+
+ /// Get the day of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).day(), 1);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .day(),
+ /// 1,
+ /// );
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.date().day()
+ }
+
+ /// Get the day of the year of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=366`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).ordinal(), 1);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .ordinal(),
+ /// 1,
+ /// );
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ self.date().ordinal()
+ }
+
+ /// Get the ISO week number of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).iso_week(), 53);
+ /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.date().iso_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).sunday_based_week(), 52);
+ /// assert_eq!(datetime!(2021-01-01 0:00 UTC).sunday_based_week(), 0);
+ /// ```
+ pub const fn sunday_based_week(self) -> u8 {
+ self.date().sunday_based_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).monday_based_week(), 52);
+ /// assert_eq!(datetime!(2021-01-01 0:00 UTC).monday_based_week(), 0);
+ /// ```
+ pub const fn monday_based_week(self) -> u8 {
+ self.date().monday_based_week()
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ self.date().to_calendar_date()
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_ordinal_date(),
+ /// (2019, 1)
+ /// );
+ /// ```
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ self.date().to_ordinal_date()
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2019, 1, Tuesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-10-04 0:00 UTC).to_iso_week_date(),
+ /// (2019, 40, Friday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-12-31 0:00 UTC).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2021-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2020, 53, Friday)
+ /// );
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ self.date().to_iso_week_date()
+ }
+
+ /// Get the weekday of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-02-01 0:00 UTC).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ self.date().weekday()
+ }
+
+ /// Get the Julian day for the date. The time is not taken into account for this calculation.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(-4713-11-24 0:00 UTC).to_julian_day(), 0);
+ /// assert_eq!(datetime!(2000-01-01 0:00 UTC).to_julian_day(), 2_451_545);
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).to_julian_day(), 2_458_485);
+ /// assert_eq!(datetime!(2019-12-31 0:00 UTC).to_julian_day(), 2_458_849);
+ /// ```
+ pub const fn to_julian_day(self) -> i32 {
+ self.date().to_julian_day()
+ }
+ // endregion date getters
+
+ // region: time getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00 UTC).to_hms(), (0, 0, 0));
+ /// assert_eq!(datetime!(2020-01-01 23:59:59 UTC).to_hms(), (23, 59, 59));
+ /// ```
+ pub const fn to_hms(self) -> (u8, u8, u8) {
+ self.time().as_hms()
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_milli(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999 UTC).to_hms_milli(),
+ /// (23, 59, 59, 999)
+ /// );
+ /// ```
+ pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) {
+ self.time().as_hms_milli()
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_micro(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999 UTC).to_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) {
+ self.time().as_hms_micro()
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_nano(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999_999 UTC).to_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) {
+ self.time().as_hms_nano()
+ }
+
+ /// Get the clock hour in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).hour(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(-2))
+ /// .hour(),
+ /// 21,
+ /// );
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.time().hour()
+ }
+
+ /// Get the minute within the hour in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).minute(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(+0:30))
+ /// .minute(),
+ /// 29,
+ /// );
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.time().minute()
+ }
+
+ /// Get the second within the minute in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).second(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(+0:00:30))
+ /// .second(),
+ /// 29,
+ /// );
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.time().second()
+ }
+
+ // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not
+ // change when adjusting for the offset.
+
+ /// Get the milliseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).millisecond(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ self.time().millisecond()
+ }
+
+ /// Get the microseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).microsecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999 UTC).microsecond(),
+ /// 999_999,
+ /// );
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.time().microsecond()
+ }
+
+ /// Get the nanoseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).nanosecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999_999 UTC).nanosecond(),
+ /// 999_999_999,
+ /// );
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.time().nanosecond()
+ }
+ // endregion time getters
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::{datetime, offset};
+ /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_add((-2).days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_add(2.days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).checked_add(27.hours()),
+ /// Some(datetime!(2019 - 11 - 26 18:30 +10))
+ /// );
+ /// ```
+ pub const fn checked_add(self, duration: Duration) -> Option<Self> {
+ Some(const_try_opt!(self.local_datetime.checked_add(duration)).assume_offset(self.offset))
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::{datetime, offset};
+ /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_sub(2.days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_sub((-2).days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).checked_sub(27.hours()),
+ /// Some(datetime!(2019 - 11 - 24 12:30 +10))
+ /// );
+ /// ```
+ pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
+ Some(const_try_opt!(self.local_datetime.checked_sub(duration)).assume_offset(self.offset))
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(-999999-01-01 0:00 +10).saturating_add((-2).days()),"
+ )]
+ #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10).saturating_add((-2).days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days()),"
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).saturating_add(27.hours()),
+ /// datetime!(2019 - 11 - 26 18:30 +10)
+ /// );
+ /// ```
+ pub const fn saturating_add(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_add(duration) {
+ datetime
+ } else if duration.is_negative() {
+ PrimitiveDateTime::MIN.assume_offset(self.offset)
+ } else {
+ debug_assert!(duration.is_positive());
+ PrimitiveDateTime::MAX.assume_offset(self.offset)
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(-999999-01-01 0:00 +10).saturating_sub(2.days()),"
+ )]
+ #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10).saturating_sub(2.days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days()),"
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).saturating_sub(27.hours()),
+ /// datetime!(2019 - 11 - 24 12:30 +10)
+ /// );
+ /// ```
+ pub const fn saturating_sub(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_sub(duration) {
+ datetime
+ } else if duration.is_negative() {
+ PrimitiveDateTime::MAX.assume_offset(self.offset)
+ } else {
+ debug_assert!(duration.is_positive());
+ PrimitiveDateTime::MIN.assume_offset(self.offset)
+ }
+ }
+ // endregion: saturating arithmetic
+}
+
+// region: replacement
+/// Methods that replace part of the `OffsetDateTime`.
+impl OffsetDateTime {
+ /// Replace the time, which is assumed to be in the stored offset. The date and offset
+ /// components are unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, time};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00)),
+ /// datetime!(2020-01-01 12:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00)),
+ /// datetime!(2020-01-01 7:00 -5)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00)),
+ /// datetime!(2020-01-01 12:00 +1)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `OffsetDateTime`."]
+ pub const fn replace_time(self, time: Time) -> Self {
+ self.local_datetime
+ .replace_time(time)
+ .assume_offset(self.offset)
+ }
+
+ /// Replace the date, which is assumed to be in the stored offset. The time and offset
+ /// components are unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, date};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 12:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 0:00 +1)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `OffsetDateTime`."]
+ pub const fn replace_date(self, date: Date) -> Self {
+ self.local_datetime
+ .replace_date(date)
+ .assume_offset(self.offset)
+ }
+
+ /// Replace the date and time, which are assumed to be in the stored offset. The offset
+ /// component remains unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00)),
+ /// datetime!(2020-01-30 16:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00)),
+ /// datetime!(2020-01-30 0:00 +1)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `OffsetDateTime`."]
+ pub const fn replace_date_time(self, date_time: PrimitiveDateTime) -> Self {
+ date_time.assume_offset(self.offset)
+ }
+
+ /// Replace the offset. The date and time components remain unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5)),
+ /// datetime!(2020-01-01 0:00 -5)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `OffsetDateTime`."]
+ pub const fn replace_offset(self, offset: UtcOffset) -> Self {
+ self.local_datetime.assume_offset(offset)
+ }
+
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_year(2019),
+ /// Ok(datetime!(2019 - 02 - 18 12:00 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year
+ /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year
+ /// ```
+ pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_year(year)).assume_offset(self.offset))
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_month(Month::January),
+ /// Ok(datetime!(2022 - 01 - 18 12:00 +01))
+ /// );
+ /// assert!(datetime!(2022 - 01 - 30 12:00 +01).replace_month(Month::February).is_err()); // 30 isn't a valid day in February
+ /// ```
+ pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_month(month)).assume_offset(self.offset))
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_day(1),
+ /// Ok(datetime!(2022 - 02 - 01 12:00 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(0).is_err()); // 00 isn't a valid day
+ /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day in February
+ /// ```
+ pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_day(day)).assume_offset(self.offset))
+ }
+
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(7),
+ /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(24).is_err()); // 24 isn't a valid hour
+ /// ```
+ pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_hour(hour)).assume_offset(self.offset))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(60).is_err()); // 60 isn't a valid minute
+ /// ```
+ pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_minute(minute)).assume_offset(self.offset))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(60).is_err()); // 60 isn't a valid second
+ /// ```
+ pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.local_datetime.replace_second(second)).assume_offset(self.offset))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(
+ const_try!(self.local_datetime.replace_millisecond(millisecond))
+ .assume_offset(self.offset),
+ )
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(7_008),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(
+ const_try!(self.local_datetime.replace_microsecond(microsecond))
+ .assume_offset(self.offset),
+ )
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(7_008_009),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond
+ /// ```
+ pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
+ Ok(
+ const_try!(self.local_datetime.replace_nanosecond(nanosecond))
+ .assume_offset(self.offset),
+ )
+ }
+}
+// endregion replacement
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl OffsetDateTime {
+ /// Format the `OffsetDateTime` using the provided [format
+ /// description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, error::Format> {
+ format.format_into(
+ output,
+ Some(self.date()),
+ Some(self.time()),
+ Some(self.offset),
+ )
+ }
+
+ /// Format the `OffsetDateTime` using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::datetime;
+ /// let format = format_description::parse(
+ /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
+ /// sign:mandatory]:[offset_minute]:[offset_second]",
+ /// )?;
+ /// assert_eq!(
+ /// datetime!(2020-01-02 03:04:05 +06:07:08).format(&format)?,
+ /// "2020-01-02 03:04:05 +06:07:08"
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(Some(self.date()), Some(self.time()), Some(self.offset))
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl OffsetDateTime {
+ /// Parse an `OffsetDateTime` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::{datetime, format_description};
+ /// let format = format_description!(
+ /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
+ /// sign:mandatory]:[offset_minute]:[offset_second]"
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::parse("2020-01-02 03:04:05 +06:07:08", &format)?,
+ /// datetime!(2020-01-02 03:04:05 +06:07:08)
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_offset_date_time(input.as_bytes())
+ }
+
+ /// A helper method to check if the `OffsetDateTime` is a valid representation of a leap second.
+ /// Leap seconds, when parsed, are represented as the preceding nanosecond. However, leap
+ /// seconds can only occur as the last second of a month UTC.
+ pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool {
+ // This comparison doesn't need to be adjusted for the stored offset, so check it first for
+ // speed.
+ if self.nanosecond() != 999_999_999 {
+ return false;
+ }
+
+ let (year, ordinal, time) = self.to_offset_raw(UtcOffset::UTC);
+
+ let date = match Date::from_ordinal_date(year, ordinal) {
+ Ok(date) => date,
+ Err(_) => return false,
+ };
+
+ time.hour() == 23
+ && time.minute() == 59
+ && time.second() == 59
+ && date.day() == util::days_in_year_month(year, date.month())
+ }
+}
+
+impl fmt::Display for OffsetDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {} {}", self.date(), self.time(), self.offset)
+ }
+}
+
+impl fmt::Debug for OffsetDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl PartialEq for OffsetDateTime {
+ fn eq(&self, rhs: &Self) -> bool {
+ self.to_offset_raw(UtcOffset::UTC) == rhs.to_offset_raw(UtcOffset::UTC)
+ }
+}
+
+impl PartialOrd for OffsetDateTime {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(self.cmp(rhs))
+ }
+}
+
+impl Ord for OffsetDateTime {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ self.to_offset_raw(UtcOffset::UTC)
+ .cmp(&rhs.to_offset_raw(UtcOffset::UTC))
+ }
+}
+
+impl Hash for OffsetDateTime {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ // We need to distinguish this from a `PrimitiveDateTime`, which would otherwise conflict.
+ hasher.write(b"OffsetDateTime");
+ self.to_offset_raw(UtcOffset::UTC).hash(hasher);
+ }
+}
+
+impl<T> Add<T> for OffsetDateTime
+where
+ PrimitiveDateTime: Add<T, Output = PrimitiveDateTime>,
+{
+ type Output = Self;
+
+ fn add(self, rhs: T) -> Self::Output {
+ (self.local_datetime + rhs).assume_offset(self.offset)
+ }
+}
+
+impl_add_assign!(OffsetDateTime: Duration, StdDuration);
+
+impl<T> Sub<T> for OffsetDateTime
+where
+ PrimitiveDateTime: Sub<T, Output = PrimitiveDateTime>,
+{
+ type Output = Self;
+
+ fn sub(self, rhs: T) -> Self::Output {
+ (self.local_datetime - rhs).assume_offset(self.offset)
+ }
+}
+
+impl_sub_assign!(OffsetDateTime: Duration, StdDuration);
+
+impl Sub for OffsetDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ let adjustment =
+ Duration::seconds((self.offset.whole_seconds() - rhs.offset.whole_seconds()) as i64);
+ self.local_datetime - rhs.local_datetime - adjustment
+ }
+}
+
+#[cfg(feature = "std")]
+impl Add<Duration> for SystemTime {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ if duration.is_zero() {
+ self
+ } else if duration.is_positive() {
+ self + duration.unsigned_abs()
+ } else {
+ debug_assert!(duration.is_negative());
+ self - duration.unsigned_abs()
+ }
+ }
+}
+
+impl_add_assign!(SystemTime: #[cfg(feature = "std")] Duration);
+
+#[cfg(feature = "std")]
+impl Sub<Duration> for SystemTime {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ (OffsetDateTime::from(self) - duration).into()
+ }
+}
+
+impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration);
+
+#[cfg(feature = "std")]
+impl Sub<SystemTime> for OffsetDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: SystemTime) -> Self::Output {
+ self - Self::from(rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Sub<OffsetDateTime> for SystemTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: OffsetDateTime) -> Self::Output {
+ OffsetDateTime::from(self) - rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<SystemTime> for OffsetDateTime {
+ fn eq(&self, rhs: &SystemTime) -> bool {
+ self == &Self::from(*rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<OffsetDateTime> for SystemTime {
+ fn eq(&self, rhs: &OffsetDateTime) -> bool {
+ &OffsetDateTime::from(*self) == rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<SystemTime> for OffsetDateTime {
+ fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
+ self.partial_cmp(&Self::from(*other))
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<OffsetDateTime> for SystemTime {
+ fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
+ OffsetDateTime::from(*self).partial_cmp(other)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<SystemTime> for OffsetDateTime {
+ 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(),
+ }
+ }
+}
+
+#[allow(clippy::fallible_impl_from)] // caused by `debug_assert!`
+#[cfg(feature = "std")]
+impl From<OffsetDateTime> for SystemTime {
+ 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()
+ }
+ }
+}
+
+#[allow(clippy::fallible_impl_from)]
+#[cfg(all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+))]
+impl From<js_sys::Date> for OffsetDateTime {
+ fn from(js_date: js_sys::Date) -> Self {
+ // get_time() returns milliseconds
+ let timestamp_nanos = (js_date.get_time() * 1_000_000.0) as i128;
+ Self::from_unix_timestamp_nanos(timestamp_nanos)
+ .expect("invalid timestamp: Timestamp cannot fit in range")
+ }
+}
+
+#[cfg(all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+))]
+impl From<OffsetDateTime> for js_sys::Date {
+ fn from(datetime: OffsetDateTime) -> Self {
+ // new Date() takes milliseconds
+ let timestamp = (datetime.unix_timestamp_nanos() / 1_000_000) as f64;
+ js_sys::Date::new(&timestamp.into())
+ }
+}
+
+// endregion trait impls
diff --git a/third_party/rust/time/src/parsing/combinator/mod.rs b/third_party/rust/time/src/parsing/combinator/mod.rs
new file mode 100644
index 0000000000..3b4bc7a81b
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/mod.rs
@@ -0,0 +1,192 @@
+//! Implementations of the low-level parser combinators.
+
+pub(crate) mod rfc;
+
+use crate::format_description::modifier::Padding;
+use crate::parsing::shim::{Integer, IntegerParseBytes};
+use crate::parsing::ParsedItem;
+
+/// Parse a "+" or "-" sign. Returns the ASCII byte representing the sign, if present.
+pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ match input {
+ [sign @ (b'-' | b'+'), remaining @ ..] => Some(ParsedItem(remaining, *sign)),
+ _ => None,
+ }
+}
+
+/// Consume the first matching item, returning its associated value.
+pub(crate) fn first_match<'a, T>(
+ options: impl IntoIterator<Item = (&'a [u8], T)>,
+ case_sensitive: bool,
+) -> impl FnMut(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ let mut options = options.into_iter();
+ move |input| {
+ options.find_map(|(expected, t)| {
+ if case_sensitive {
+ Some(ParsedItem(input.strip_prefix(expected)?, t))
+ } else {
+ let n = expected.len();
+ if n <= input.len() {
+ let (head, tail) = input.split_at(n);
+ if head.eq_ignore_ascii_case(expected) {
+ return Some(ParsedItem(tail, t));
+ }
+ }
+ None
+ }
+ })
+ }
+}
+
+/// Consume zero or more instances of the provided parser. The parser must return the unit value.
+pub(crate) fn zero_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
+ parser: P,
+) -> impl FnMut(&'a [u8]) -> 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.
+pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
+ parser: P,
+) -> impl 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` instances of the provided parser.
+pub(crate) fn n_to_m<
+ 'a,
+ const N: u8,
+ const M: u8,
+ T,
+ P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
+>(
+ parser: P,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> {
+ debug_assert!(M >= N);
+ move |mut input| {
+ // We need to keep this to determine the total length eventually consumed.
+ let orig_input = input;
+
+ // Mandatory
+ for _ in 0..N {
+ input = parser(input)?.0;
+ }
+
+ // Optional
+ for _ in N..M {
+ match parser(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+
+ Some(ParsedItem(
+ input,
+ &orig_input[..(orig_input.len() - input.len())],
+ ))
+ }
+}
+
+/// Consume between `n` and `m` digits, returning the numerical value.
+pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T: Integer>(
+ input: &[u8],
+) -> Option<ParsedItem<'_, T>> {
+ debug_assert!(M >= N);
+ n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
+}
+
+/// Consume exactly `n` digits, returning the numerical value.
+pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
+ n_to_m_digits::<N, N, _>(input)
+}
+
+/// Consume exactly `n` digits, returning the numerical value.
+pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>(
+ padding: Padding,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ 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<'a, const N: u8, const M: u8, T: Integer>(
+ padding: Padding,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ debug_assert!(M >= N);
+ move |mut input| match padding {
+ Padding::None => n_to_m_digits::<1, M, _>(input),
+ Padding::Space => {
+ debug_assert!(N > 0);
+
+ let mut orig_input = input;
+ for _ in 0..(N - 1) {
+ match ascii_char::<b' '>(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+ let pad_width = (orig_input.len() - input.len()) as u8;
+
+ orig_input = input;
+ for _ in 0..(N - pad_width) {
+ input = any_digit(input)?.0;
+ }
+ for _ in N..M {
+ match any_digit(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+
+ ParsedItem(input, &orig_input[..(orig_input.len() - input.len())])
+ .flat_map(|value| value.parse_bytes())
+ }
+ Padding::Zero => n_to_m_digits::<N, M, _>(input),
+ }
+}
+
+/// Consume exactly one digit.
+pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ match input {
+ [c, remaining @ ..] if c.is_ascii_digit() => Some(ParsedItem(remaining, *c)),
+ _ => None,
+ }
+}
+
+/// Consume exactly one of the provided ASCII characters.
+pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ debug_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.
+pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ debug_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.
+pub(crate) fn opt<'a, T>(
+ parser: impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
+) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
+ move |input| match parser(input) {
+ Some(value) => value.map(Some),
+ None => ParsedItem(input, None),
+ }
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs
new file mode 100644
index 0000000000..613a9057ff
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs
@@ -0,0 +1,173 @@
+//! Rules defined in [ISO 8601].
+//!
+//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
+
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign};
+use crate::parsing::ParsedItem;
+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?
+ pub(crate) const fn maybe_extended(self) -> bool {
+ matches!(self, Self::Extended | Self::Unknown)
+ }
+
+ /// Is the format known for certain to be extended?
+ 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`.
+ pub(crate) 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`.
+ pub(crate) 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.
+pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> {
+ Some(match sign(input) {
+ Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| {
+ let val = val as i32;
+ if sign == b'-' { -val } else { val }
+ }),
+ None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _),
+ })
+}
+
+/// Parse a month.
+pub(crate) fn month(input: &[u8]) -> Option<ParsedItem<'_, Month>> {
+ first_match(
+ [
+ (b"01".as_slice(), Month::January),
+ (b"02".as_slice(), Month::February),
+ (b"03".as_slice(), Month::March),
+ (b"04".as_slice(), Month::April),
+ (b"05".as_slice(), Month::May),
+ (b"06".as_slice(), Month::June),
+ (b"07".as_slice(), Month::July),
+ (b"08".as_slice(), Month::August),
+ (b"09".as_slice(), Month::September),
+ (b"10".as_slice(), Month::October),
+ (b"11".as_slice(), Month::November),
+ (b"12".as_slice(), Month::December),
+ ],
+ true,
+ )(input)
+}
+
+/// Parse a week number.
+pub(crate) fn week(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the month.
+pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the week.
+pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> {
+ first_match(
+ [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"7".as_slice(), Weekday::Sunday),
+ ],
+ true,
+ )(input)
+}
+
+/// Parse a day of the year.
+pub(crate) fn dayo(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU16>> {
+ exactly_n_digits::<3, _>(input)
+}
+
+/// Parse the hour.
+pub(crate) fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse the minute.
+pub(crate) fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits::<2, _>(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.
+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.
+fn decimal_sign(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ ascii_char::<b'.'>(input).or_else(|| ascii_char::<b','>(input))
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/mod.rs b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs
new file mode 100644
index 0000000000..2974a4d5c4
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs
@@ -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;
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs
new file mode 100644
index 0000000000..6753444358
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs
@@ -0,0 +1,13 @@
+//! Rules defined in [RFC 2234].
+//!
+//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
+
+use crate::parsing::ParsedItem;
+
+/// Consume exactly one space or tab.
+pub(crate) const fn wsp(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ match input {
+ [b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => None,
+ }
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs
new file mode 100644
index 0000000000..8410de06e3
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs
@@ -0,0 +1,115 @@
+//! Rules defined in [RFC 2822].
+//!
+//! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
+
+use crate::parsing::combinator::rfc::rfc2234::wsp;
+use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more};
+use crate::parsing::ParsedItem;
+
+/// Consume the `fws` rule.
+// The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/
+pub(crate) fn fws(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, ()))
+ }
+}
+
+/// Consume the `cfws` rule.
+// The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty.
+pub(crate) fn cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ one_or_more(|input| fws(input).or_else(|| comment(input)))(input)
+}
+
+/// Consume the `comment` rule.
+fn comment(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'('>(input)?.into_inner();
+ input = zero_or_more(fws)(input).into_inner();
+ while let Some(rest) = ccontent(input) {
+ 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.
+fn ccontent(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ ctext(input)
+ .or_else(|| quoted_pair(input))
+ .or_else(|| comment(input))
+}
+
+/// Consume the `ctext` rule.
+#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+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.
+fn quoted_pair(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'\\'>(input)?.into_inner();
+
+ let old_input_len = input.len();
+
+ input = text(input).into_inner();
+
+ // If nothing is parsed, this means we hit the `obs-text` rule and nothing matched. This is
+ // technically a success, but we should still check the `obs-qp` rule to ensure we consume
+ // everything possible.
+ if input.len() == old_input_len {
+ match input {
+ [0..=127, rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => Some(ParsedItem(input, ())),
+ }
+ } else {
+ Some(ParsedItem(input, ()))
+ }
+}
+
+/// Consume the `no_ws_ctl` rule.
+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.
+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))
+}
diff --git a/third_party/rust/time/src/parsing/component.rs b/third_party/rust/time/src/parsing/component.rs
new file mode 100644
index 0000000000..38d632a0da
--- /dev/null
+++ b/third_party/rust/time/src/parsing/component.rs
@@ -0,0 +1,296 @@
+//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
+
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::format_description::modifier;
+#[cfg(feature = "large-dates")]
+use crate::parsing::combinator::n_to_m_digits_padded;
+use crate::parsing::combinator::{
+ any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, opt, sign,
+};
+use crate::parsing::ParsedItem;
+use crate::{Month, Weekday};
+
+// region: date components
+/// Parse the "year" component of a `Date`.
+pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<ParsedItem<'_, i32>> {
+ match modifiers.repr {
+ modifier::YearRepr::Full => {
+ let ParsedItem(input, sign) = opt(sign)(input);
+ #[cfg(not(feature = "large-dates"))]
+ let ParsedItem(input, year) =
+ exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
+ #[cfg(feature = "large-dates")]
+ let ParsedItem(input, year) =
+ n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
+ match sign {
+ Some(b'-') => Some(ParsedItem(input, -(year as i32))),
+ None if modifiers.sign_is_mandatory || year >= 10_000 => None,
+ _ => Some(ParsedItem(input, year as i32)),
+ }
+ }
+ modifier::YearRepr::LastTwo => {
+ Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32))
+ }
+ }
+}
+
+/// Parse the "month" component of a `Date`.
+pub(crate) fn parse_month(
+ input: &[u8],
+ modifiers: modifier::Month,
+) -> Option<ParsedItem<'_, Month>> {
+ use Month::*;
+ let ParsedItem(remaining, value) = first_match(
+ match modifiers.repr {
+ modifier::MonthRepr::Numerical => {
+ return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
+ .flat_map(|n| Month::from_number(n).ok());
+ }
+ modifier::MonthRepr::Long => [
+ (b"January".as_slice(), January),
+ (b"February".as_slice(), February),
+ (b"March".as_slice(), March),
+ (b"April".as_slice(), April),
+ (b"May".as_slice(), May),
+ (b"June".as_slice(), June),
+ (b"July".as_slice(), July),
+ (b"August".as_slice(), August),
+ (b"September".as_slice(), September),
+ (b"October".as_slice(), October),
+ (b"November".as_slice(), November),
+ (b"December".as_slice(), December),
+ ],
+ modifier::MonthRepr::Short => [
+ (b"Jan".as_slice(), January),
+ (b"Feb".as_slice(), February),
+ (b"Mar".as_slice(), March),
+ (b"Apr".as_slice(), April),
+ (b"May".as_slice(), May),
+ (b"Jun".as_slice(), June),
+ (b"Jul".as_slice(), July),
+ (b"Aug".as_slice(), August),
+ (b"Sep".as_slice(), September),
+ (b"Oct".as_slice(), October),
+ (b"Nov".as_slice(), November),
+ (b"Dec".as_slice(), December),
+ ],
+ },
+ modifiers.case_sensitive,
+ )(input)?;
+ Some(ParsedItem(remaining, value))
+}
+
+/// Parse the "week number" component of a `Date`.
+pub(crate) fn parse_week_number(
+ input: &[u8],
+ modifiers: modifier::WeekNumber,
+) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "weekday" component of a `Date`.
+pub(crate) fn parse_weekday(
+ input: &[u8],
+ modifiers: modifier::Weekday,
+) -> Option<ParsedItem<'_, Weekday>> {
+ first_match(
+ match (modifiers.repr, modifiers.one_indexed) {
+ (modifier::WeekdayRepr::Short, _) => [
+ (b"Mon".as_slice(), Weekday::Monday),
+ (b"Tue".as_slice(), Weekday::Tuesday),
+ (b"Wed".as_slice(), Weekday::Wednesday),
+ (b"Thu".as_slice(), Weekday::Thursday),
+ (b"Fri".as_slice(), Weekday::Friday),
+ (b"Sat".as_slice(), Weekday::Saturday),
+ (b"Sun".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Long, _) => [
+ (b"Monday".as_slice(), Weekday::Monday),
+ (b"Tuesday".as_slice(), Weekday::Tuesday),
+ (b"Wednesday".as_slice(), Weekday::Wednesday),
+ (b"Thursday".as_slice(), Weekday::Thursday),
+ (b"Friday".as_slice(), Weekday::Friday),
+ (b"Saturday".as_slice(), Weekday::Saturday),
+ (b"Sunday".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Sunday, false) => [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"0".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Sunday, true) => [
+ (b"2".as_slice(), Weekday::Monday),
+ (b"3".as_slice(), Weekday::Tuesday),
+ (b"4".as_slice(), Weekday::Wednesday),
+ (b"5".as_slice(), Weekday::Thursday),
+ (b"6".as_slice(), Weekday::Friday),
+ (b"7".as_slice(), Weekday::Saturday),
+ (b"1".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Monday, false) => [
+ (b"0".as_slice(), Weekday::Monday),
+ (b"1".as_slice(), Weekday::Tuesday),
+ (b"2".as_slice(), Weekday::Wednesday),
+ (b"3".as_slice(), Weekday::Thursday),
+ (b"4".as_slice(), Weekday::Friday),
+ (b"5".as_slice(), Weekday::Saturday),
+ (b"6".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Monday, true) => [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"7".as_slice(), Weekday::Sunday),
+ ],
+ },
+ modifiers.case_sensitive,
+ )(input)
+}
+
+/// Parse the "ordinal" component of a `Date`.
+pub(crate) fn parse_ordinal(
+ input: &[u8],
+ modifiers: modifier::Ordinal,
+) -> Option<ParsedItem<'_, NonZeroU16>> {
+ exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
+}
+
+/// Parse the "day" component of a `Date`.
+pub(crate) fn parse_day(
+ input: &[u8],
+ modifiers: modifier::Day,
+) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+// endregion date components
+
+// region: time components
+/// 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,
+}
+
+/// Parse the "hour" component of a `Time`.
+pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "minute" component of a `Time`.
+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`.
+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.
+pub(crate) fn parse_period(
+ input: &[u8],
+ modifiers: modifier::Period,
+) -> Option<ParsedItem<'_, Period>> {
+ first_match(
+ if modifiers.is_uppercase {
+ [
+ (b"AM".as_slice(), Period::Am),
+ (b"PM".as_slice(), Period::Pm),
+ ]
+ } else {
+ [
+ (b"am".as_slice(), Period::Am),
+ (b"pm".as_slice(), Period::Pm),
+ ]
+ },
+ modifiers.case_sensitive,
+ )(input)
+}
+
+/// 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 => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
+ Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
+ Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
+ Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
+ Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
+ Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
+ Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
+ Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
+ Nine => exactly_n_digits::<9, _>(input)?,
+ OneOrMore => {
+ let ParsedItem(mut input, mut value) =
+ any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ input = new_input;
+ multiplier /= 10;
+ }
+
+ ParsedItem(input, value)
+ }
+ })
+}
+// endregion time components
+
+// region: offset components
+/// Parse the "hour" component of a `UtcOffset`.
+pub(crate) fn parse_offset_hour(
+ input: &[u8],
+ modifiers: modifier::OffsetHour,
+) -> Option<ParsedItem<'_, i8>> {
+ let ParsedItem(input, sign) = opt(sign)(input);
+ let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
+ match sign {
+ Some(b'-') => Some(ParsedItem(input, -(hour as i8))),
+ None if modifiers.sign_is_mandatory => None,
+ _ => Some(ParsedItem(input, hour as i8)),
+ }
+}
+
+/// Parse the "minute" component of a `UtcOffset`.
+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 as _),
+ )
+}
+
+/// Parse the "second" component of a `UtcOffset`.
+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 as _),
+ )
+}
+// endregion offset components
diff --git a/third_party/rust/time/src/parsing/iso8601.rs b/third_party/rust/time/src/parsing/iso8601.rs
new file mode 100644
index 0000000000..21c89bab3f
--- /dev/null
+++ b/third_party/rust/time/src/parsing/iso8601.rs
@@ -0,0 +1,308 @@
+//! Parse parts of an ISO 8601-formatted value.
+
+use crate::error;
+use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::Iso8601;
+use crate::parsing::combinator::rfc::iso8601::{
+ day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
+};
+use crate::parsing::combinator::{ascii_char, sign};
+use crate::parsing::{Parsed, ParsedItem};
+
+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> + 'a {
+ move |input| {
+ // Same for any acceptable format.
+ let ParsedItem(mut input, year) = 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 mut ret_error = match (|| {
+ let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
+ if extended_kind.is_extended() {
+ input = ascii_char::<b'-'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ }
+ let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
+ Ok(ParsedItem(input, (month, day)))
+ })() {
+ Ok(ParsedItem(input, (month, day))) => {
+ *parsed = 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 = parsed
+ .with_year(year)
+ .ok_or(InvalidComponent("year"))?
+ .with_ordinal(ordinal)
+ .ok_or(InvalidComponent("ordinal"))?;
+ return Ok(input);
+ }
+
+ match (|| {
+ let input = ascii_char::<b'W'>(input)
+ .ok_or((false, InvalidLiteral))?
+ .into_inner();
+ let ParsedItem(mut input, week) =
+ week(input).ok_or((true, InvalidComponent("week")))?;
+ if extended_kind.is_extended() {
+ input = ascii_char::<b'-'>(input)
+ .ok_or((true, InvalidLiteral))?
+ .into_inner();
+ }
+ let ParsedItem(input, weekday) =
+ dayk(input).ok_or((true, InvalidComponent("weekday")))?;
+ Ok(ParsedItem(input, (week, weekday)))
+ })() {
+ Ok(ParsedItem(input, (week, weekday))) => {
+ *parsed = 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> + 'a {
+ move |mut input| {
+ if date_is_present {
+ input = ascii_char::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ }
+
+ let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
+ match hour {
+ (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
+ (hour, Some(fractional_part)) => {
+ *parsed = parsed
+ .with_hour_24(hour)
+ .ok_or(InvalidComponent("hour"))?
+ .with_minute((fractional_part * 60.0) as _)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second((fractional_part * 3600.0 % 60.) as _)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _,
+ )
+ .ok_or(InvalidComponent("subsecond"))?;
+ return Ok(input);
+ }
+ };
+
+ if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
+ 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();
+ 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 = parsed
+ .with_minute(minute)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second((fractional_part * 60.) as _)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _,
+ )
+ .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 = 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 = 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 * 1_000_000_000.) as _)
+ }
+ 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 = 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> + 'a {
+ move |input| {
+ if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
+ *parsed = 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) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let mut input = hour(input)
+ .and_then(|parsed_item| {
+ parsed_item.consume_value(|hour| {
+ parsed.set_offset_hour(if sign == b'-' {
+ -(hour as i8)
+ } else {
+ hour as _
+ })
+ })
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+
+ if extended_kind.maybe_extended() {
+ if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
+ extended_kind
+ .coerce_extended()
+ .ok_or(InvalidComponent("offset minute"))?;
+ input = new_input;
+ };
+ }
+
+ let input = min(input)
+ .and_then(|parsed_item| {
+ parsed_item.consume_value(|min| {
+ parsed.set_offset_minute_signed(if sign == b'-' {
+ -(min as i8)
+ } else {
+ min as _
+ })
+ })
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+ // 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`
+fn round(value: f64) -> f64 {
+ #[cfg(feature = "std")]
+ {
+ value.round()
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ round_impl(value)
+ }
+}
+
+#[cfg(not(feature = "std"))]
+#[allow(clippy::missing_docs_in_private_items)]
+fn round_impl(value: f64) -> f64 {
+ debug_assert!(value.is_sign_positive() && !value.is_nan());
+
+ let f = value % 1.;
+ if f < 0.5 { value - f } else { value - f + 1. }
+}
diff --git a/third_party/rust/time/src/parsing/mod.rs b/third_party/rust/time/src/parsing/mod.rs
new file mode 100644
index 0000000000..4a2aa829f1
--- /dev/null
+++ b/third_party/rust/time/src/parsing/mod.rs
@@ -0,0 +1,50 @@
+//! 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.
+ 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.
+ 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"]
+ pub(crate) fn consume_value(self, f: impl FnOnce(T) -> Option<()>) -> Option<&'a [u8]> {
+ f(self.1)?;
+ Some(self.0)
+ }
+}
+
+impl<'a> ParsedItem<'a, ()> {
+ /// Discard the unit value, returning the remaining input.
+ #[must_use = "this returns the remaining input"]
+ pub(crate) const fn into_inner(self) -> &'a [u8] {
+ self.0
+ }
+}
+
+impl<'a> ParsedItem<'a, Option<()>> {
+ /// Discard the potential unit value, returning the remaining input.
+ #[must_use = "this returns the remaining input"]
+ pub(crate) const fn into_inner(self) -> &'a [u8] {
+ self.0
+ }
+}
diff --git a/third_party/rust/time/src/parsing/parsable.rs b/third_party/rust/time/src/parsing/parsable.rs
new file mode 100644
index 0000000000..badb638088
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsable.rs
@@ -0,0 +1,754 @@
+//! A trait that can be used to parse an item from an input.
+
+use core::ops::Deref;
+
+use crate::error::TryFromParsed;
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
+use crate::format_description::FormatItem;
+#[cfg(feature = "alloc")]
+use crate::format_description::OwnedFormatItem;
+use crate::parsing::{Parsed, ParsedItem};
+use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// A type that can be parsed.
+#[cfg_attr(__time_03_docs, doc(notable_trait))]
+pub trait Parsable: sealed::Sealed {}
+impl Parsable for FormatItem<'_> {}
+impl Parsable for [FormatItem<'_>] {}
+#[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: Deref> Parsable for T where T::Target: Parsable {}
+
+/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
+/// exist in generic bounds.
+mod sealed {
+
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Parse the item using a format description and an input.
+ 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,
+ ) -> Result<&'a [u8], error::Parse>;
+
+ /// Parse the item into a new [`Parsed`] struct.
+ ///
+ /// 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.
+ fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
+ let mut parsed = Parsed::new();
+ if self.parse_into(input, &mut parsed)?.is_empty() {
+ Ok(parsed)
+ } else {
+ Err(error::Parse::UnexpectedTrailingCharacters)
+ }
+ }
+
+ /// Parse a [`Date`] from the format description.
+ fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`Time`] from the format description.
+ fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`UtcOffset`] from the format description.
+ fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`PrimitiveDateTime`] from the format description.
+ fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`OffsetDateTime`] from the format description.
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+ }
+}
+
+// region: custom formats
+impl sealed::Sealed for FormatItem<'_> {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_item(input, self)?)
+ }
+}
+
+impl sealed::Sealed for [FormatItem<'_>] {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_items(input, self)?)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::Sealed for OwnedFormatItem {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_item(input, self)?)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::Sealed for [OwnedFormatItem] {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_items(input, self)?)
+ }
+}
+
+impl<T: Deref> sealed::Sealed for T
+where
+ T::Target: sealed::Sealed,
+{
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ self.deref().parse_into(input, parsed)
+ }
+}
+// endregion custom formats
+
+// region: well-known formats
+impl sealed::Sealed for Rfc2822 {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
+ use crate::parsing::combinator::{
+ ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
+ };
+
+ let colon = ascii_char::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ let input = opt(fws)(input).into_inner();
+ let input = first_match(
+ [
+ (b"Mon".as_slice(), Weekday::Monday),
+ (b"Tue".as_slice(), Weekday::Tuesday),
+ (b"Wed".as_slice(), Weekday::Wednesday),
+ (b"Thu".as_slice(), Weekday::Thursday),
+ (b"Fri".as_slice(), Weekday::Friday),
+ (b"Sat".as_slice(), Weekday::Saturday),
+ (b"Sun".as_slice(), Weekday::Sunday),
+ ],
+ false,
+ )(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_weekday(value)))
+ .ok_or(InvalidComponent("weekday"))?;
+ let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = n_to_m_digits::<1, 2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
+ .ok_or(InvalidComponent("day"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = first_match(
+ [
+ (b"Jan".as_slice(), Month::January),
+ (b"Feb".as_slice(), Month::February),
+ (b"Mar".as_slice(), Month::March),
+ (b"Apr".as_slice(), Month::April),
+ (b"May".as_slice(), Month::May),
+ (b"Jun".as_slice(), Month::June),
+ (b"Jul".as_slice(), Month::July),
+ (b"Aug".as_slice(), Month::August),
+ (b"Sep".as_slice(), Month::September),
+ (b"Oct".as_slice(), Month::October),
+ (b"Nov".as_slice(), Month::November),
+ (b"Dec".as_slice(), Month::December),
+ ],
+ false,
+ )(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
+ .ok_or(InvalidComponent("month"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = match exactly_n_digits::<4, u32>(input) {
+ Some(item) => {
+ let input = item
+ .flat_map(|year| if year >= 1900 { Some(year) } else { None })
+ .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
+ .ok_or(InvalidComponent("year"))?;
+ fws(input).ok_or(InvalidLiteral)?.into_inner()
+ }
+ None => {
+ let input = exactly_n_digits::<2, u32>(input)
+ .and_then(|item| {
+ item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
+ .map(|year| year as _)
+ .consume_value(|value| parsed.set_year(value))
+ })
+ .ok_or(InvalidComponent("year"))?;
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ }
+ };
+
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
+ .ok_or(InvalidComponent("hour"))?;
+ let input = opt(cfws)(input).into_inner();
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = opt(cfws)(input).into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
+ .ok_or(InvalidComponent("minute"))?;
+
+ let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
+ let input = input.into_inner(); // discard the colon
+ let input = opt(cfws)(input).into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
+ .ok_or(InvalidComponent("second"))?;
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ } else {
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ };
+
+ // The RFC explicitly allows leap seconds.
+ parsed.set_leap_second_allowed(true);
+
+ #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+ let zone_literal = first_match(
+ [
+ (b"UT".as_slice(), 0),
+ (b"GMT".as_slice(), 0),
+ (b"EST".as_slice(), -5),
+ (b"EDT".as_slice(), -4),
+ (b"CST".as_slice(), -6),
+ (b"CDT".as_slice(), -5),
+ (b"MST".as_slice(), -7),
+ (b"MDT".as_slice(), -6),
+ (b"PST".as_slice(), -8),
+ (b"PDT".as_slice(), -7),
+ ],
+ false,
+ )(input)
+ .or_else(|| match input {
+ [
+ b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
+ rest @ ..,
+ ] => Some(ParsedItem(rest, 0)),
+ _ => None,
+ });
+ if let Some(zone_literal) = zone_literal {
+ let input = zone_literal
+ .consume_value(|value| parsed.set_offset_hour(value))
+ .ok_or(InvalidComponent("offset hour"))?;
+ parsed
+ .set_offset_minute_signed(0)
+ .ok_or(InvalidComponent("offset minute"))?;
+ parsed
+ .set_offset_second_signed(0)
+ .ok_or(InvalidComponent("offset second"))?;
+ return Ok(input);
+ }
+
+ let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_hour(value))
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+
+ Ok(input)
+ }
+
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
+ use crate::parsing::combinator::{
+ ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
+ };
+
+ let colon = ascii_char::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ let input = opt(fws)(input).into_inner();
+ // This parses the weekday, but we don't actually use the value anywhere. Because of this,
+ // just return `()` to avoid unnecessary generated code.
+ let ParsedItem(input, ()) = first_match(
+ [
+ (b"Mon".as_slice(), ()),
+ (b"Tue".as_slice(), ()),
+ (b"Wed".as_slice(), ()),
+ (b"Thu".as_slice(), ()),
+ (b"Fri".as_slice(), ()),
+ (b"Sat".as_slice(), ()),
+ (b"Sun".as_slice(), ()),
+ ],
+ false,
+ )(input)
+ .ok_or(InvalidComponent("weekday"))?;
+ let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, day) =
+ n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, month) = first_match(
+ [
+ (b"Jan".as_slice(), Month::January),
+ (b"Feb".as_slice(), Month::February),
+ (b"Mar".as_slice(), Month::March),
+ (b"Apr".as_slice(), Month::April),
+ (b"May".as_slice(), Month::May),
+ (b"Jun".as_slice(), Month::June),
+ (b"Jul".as_slice(), Month::July),
+ (b"Aug".as_slice(), Month::August),
+ (b"Sep".as_slice(), Month::September),
+ (b"Oct".as_slice(), Month::October),
+ (b"Nov".as_slice(), Month::November),
+ (b"Dec".as_slice(), Month::December),
+ ],
+ false,
+ )(input)
+ .ok_or(InvalidComponent("month"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let (input, year) = match exactly_n_digits::<4, u32>(input) {
+ Some(item) => {
+ let ParsedItem(input, year) = item
+ .flat_map(|year| if year >= 1900 { Some(year) } else { None })
+ .ok_or(InvalidComponent("year"))?;
+ let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, year)
+ }
+ None => {
+ let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input)
+ .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
+ .ok_or(InvalidComponent("year"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, year)
+ }
+ };
+
+ let ParsedItem(input, hour) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
+ let input = opt(cfws)(input).into_inner();
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = opt(cfws)(input).into_inner();
+ let ParsedItem(input, minute) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
+
+ let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
+ let input = input.into_inner(); // discard the colon
+ let input = opt(cfws)(input).into_inner();
+ let ParsedItem(input, second) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, second)
+ } else {
+ (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
+ };
+
+ #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+ let zone_literal = first_match(
+ [
+ (b"UT".as_slice(), 0),
+ (b"GMT".as_slice(), 0),
+ (b"EST".as_slice(), -5),
+ (b"EDT".as_slice(), -4),
+ (b"CST".as_slice(), -6),
+ (b"CDT".as_slice(), -5),
+ (b"MST".as_slice(), -7),
+ (b"MDT".as_slice(), -6),
+ (b"PST".as_slice(), -8),
+ (b"PDT".as_slice(), -7),
+ ],
+ false,
+ )(input)
+ .or_else(|| match input {
+ [
+ b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
+ rest @ ..,
+ ] => Some(ParsedItem(rest, 0)),
+ _ => None,
+ });
+
+ let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
+ let ParsedItem(input, offset_hour) = zone_literal;
+ (input, offset_hour, 0)
+ } else {
+ let ParsedItem(input, offset_sign) =
+ sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
+ .map(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_minute) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
+ (input, offset_hour, offset_minute as i8)
+ };
+
+ if !input.is_empty() {
+ return Err(error::Parse::UnexpectedTrailingCharacters);
+ }
+
+ let mut nanosecond = 0;
+ let leap_second_input = if second == 60 {
+ second = 59;
+ nanosecond = 999_999_999;
+ true
+ } else {
+ false
+ };
+
+ let dt = (|| {
+ let date = Date::from_calendar_date(year as _, month, day)?;
+ let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
+ let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
+ Ok(date.with_time(time).assume_offset(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 {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ )));
+ }
+
+ Ok(dt)
+ }
+}
+
+impl sealed::Sealed for Rfc3339 {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::{
+ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
+ };
+
+ let dash = ascii_char::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ let input = exactly_n_digits::<4, u32>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
+ .ok_or(InvalidComponent("year"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
+ .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
+ .ok_or(InvalidComponent("month"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
+ .ok_or(InvalidComponent("day"))?;
+ let input = ascii_char_ignore_case::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
+ .ok_or(InvalidComponent("hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
+ .ok_or(InvalidComponent("minute"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(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) = any_digit(input)
+ .ok_or(InvalidComponent("subsecond"))?
+ .map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ input = new_input;
+ multiplier /= 10;
+ }
+
+ parsed
+ .set_subsecond(value)
+ .ok_or(InvalidComponent("subsecond"))?;
+ input
+ } else {
+ input
+ };
+
+ // The RFC explicitly allows leap seconds.
+ parsed.set_leap_second_allowed(true);
+
+ if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
+ parsed
+ .set_offset_hour(0)
+ .ok_or(InvalidComponent("offset hour"))?;
+ parsed
+ .set_offset_minute_signed(0)
+ .ok_or(InvalidComponent("offset minute"))?;
+ parsed
+ .set_offset_second_signed(0)
+ .ok_or(InvalidComponent("offset second"))?;
+ return Ok(input);
+ }
+
+ let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_hour(value))
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_minute| {
+ if offset_sign == b'-' {
+ -(offset_minute as i8)
+ } else {
+ offset_minute as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_minute_signed(value))
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+
+ Ok(input)
+ }
+
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::{
+ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
+ };
+
+ let dash = ascii_char::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ let ParsedItem(input, year) =
+ exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, month) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, day) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?;
+ let input = ascii_char_ignore_case::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ let ParsedItem(input, hour) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, minute) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, mut second) =
+ exactly_n_digits::<2, _>(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) = any_digit(input)
+ .ok_or(InvalidComponent("subsecond"))?
+ .map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ 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) =
+ sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_hour) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, offset_minute) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
+ UtcOffset::from_hms(
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ },
+ if offset_sign == b'-' {
+ -(offset_minute as i8)
+ } else {
+ offset_minute as _
+ },
+ 0,
+ )
+ .map(|offset| ParsedItem(input, offset))
+ .map_err(|mut err| {
+ // Provide the user a more accurate error.
+ if err.name == "hours" {
+ err.name = "offset hour";
+ } else if err.name == "minutes" {
+ err.name = "offset minute";
+ }
+ err
+ })
+ .map_err(TryFromParsed::ComponentRange)?
+ }
+ };
+
+ if !input.is_empty() {
+ return Err(error::Parse::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 dt = Month::from_number(month)
+ .and_then(|month| Date::from_calendar_date(year as _, month, day))
+ .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
+ .map(|date| date.assume_offset(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 {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ )));
+ }
+
+ Ok(dt)
+ }
+}
+
+impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
+ fn parse_into<'a>(
+ &self,
+ mut input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> 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;
+
+ 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 => unreachable!("an error should be present if no components were parsed"),
+ }
+ }
+
+ Ok(input)
+ }
+}
+// endregion well-known formats
diff --git a/third_party/rust/time/src/parsing/parsed.rs b/third_party/rust/time/src/parsing/parsed.rs
new file mode 100644
index 0000000000..7b2279cbb1
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsed.rs
@@ -0,0 +1,759 @@
+//! Information parsed from an input and format description.
+
+use core::mem::MaybeUninit;
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::error::TryFromParsed::InsufficientInformation;
+use crate::format_description::modifier::{WeekNumberRepr, YearRepr};
+#[cfg(feature = "alloc")]
+use crate::format_description::OwnedFormatItem;
+use crate::format_description::{Component, FormatItem};
+use crate::parsing::component::{
+ parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute,
+ parse_offset_second, parse_ordinal, parse_period, parse_second, parse_subsecond,
+ parse_week_number, parse_weekday, parse_year, Period,
+};
+use crate::parsing::ParsedItem;
+use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// Sealed to prevent downstream implementations.
+mod sealed {
+ use super::*;
+
+ /// A trait to allow `parse_item` to be generic.
+ pub trait AnyFormatItem {
+ /// Parse a single item, returning the remaining input on success.
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription>;
+ }
+}
+
+impl sealed::AnyFormatItem for FormatItem<'_> {
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ match self {
+ Self::Literal(literal) => Parsed::parse_literal(input, literal),
+ Self::Component(component) => parsed.parse_component(input, *component),
+ Self::Compound(compound) => parsed.parse_items(input, compound),
+ Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)),
+ Self::First(items) => {
+ let mut first_err = None;
+
+ for item in items.iter() {
+ match parsed.parse_item(input, item) {
+ Ok(remaining_input) => return Ok(remaining_input),
+ Err(err) if first_err.is_none() => first_err = Some(err),
+ Err(_) => {}
+ }
+ }
+
+ match first_err {
+ Some(err) => Err(err),
+ // This location will be reached if the slice is empty, skipping the `for` loop.
+ // As this case is expected to be uncommon, there's no need to check up front.
+ None => Ok(input),
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::AnyFormatItem for OwnedFormatItem {
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ match self {
+ Self::Literal(literal) => Parsed::parse_literal(input, literal),
+ Self::Component(component) => parsed.parse_component(input, *component),
+ Self::Compound(compound) => parsed.parse_items(input, compound),
+ Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)),
+ Self::First(items) => {
+ let mut first_err = None;
+
+ for item in items.iter() {
+ match parsed.parse_item(input, item) {
+ Ok(remaining_input) => return Ok(remaining_input),
+ Err(err) if first_err.is_none() => first_err = Some(err),
+ Err(_) => {}
+ }
+ }
+
+ match first_err {
+ Some(err) => Err(err),
+ // This location will be reached if the slice is empty, skipping the `for` loop.
+ // As this case is expected to be uncommon, there's no need to check up front.
+ None => Ok(input),
+ }
+ }
+ }
+ }
+}
+
+/// All information parsed.
+///
+/// This information is directly used to construct the final values.
+///
+/// Most users will not need think about this struct in any way. It is public to allow for manual
+/// control over values, in the instance that the default parser is insufficient.
+#[derive(Debug, Clone, Copy)]
+pub struct Parsed {
+ /// Bitflags indicating whether a particular field is present.
+ flags: u16,
+ /// Calendar year.
+ year: MaybeUninit<i32>,
+ /// The last two digits of the calendar year.
+ year_last_two: MaybeUninit<u8>,
+ /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
+ iso_year: MaybeUninit<i32>,
+ /// The last two digits of the ISO week year.
+ iso_year_last_two: MaybeUninit<u8>,
+ /// Month of the year.
+ month: Option<Month>,
+ /// Week of the year, where week one begins on the first Sunday of the calendar year.
+ sunday_week_number: MaybeUninit<u8>,
+ /// Week of the year, where week one begins on the first Monday of the calendar year.
+ monday_week_number: MaybeUninit<u8>,
+ /// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
+ iso_week_number: Option<NonZeroU8>,
+ /// Day of the week.
+ weekday: Option<Weekday>,
+ /// Day of the year.
+ ordinal: Option<NonZeroU16>,
+ /// Day of the month.
+ day: Option<NonZeroU8>,
+ /// Hour within the day.
+ hour_24: MaybeUninit<u8>,
+ /// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
+ /// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
+ hour_12: Option<NonZeroU8>,
+ /// Whether the `hour_12` field indicates a time that "PM".
+ hour_12_is_pm: Option<bool>,
+ /// Minute within the hour.
+ minute: MaybeUninit<u8>,
+ /// Second within the minute.
+ second: MaybeUninit<u8>,
+ /// Nanosecond within the second.
+ subsecond: MaybeUninit<u32>,
+ /// Whole hours of the UTC offset.
+ offset_hour: MaybeUninit<i8>,
+ /// Minutes within the hour of the UTC offset.
+ offset_minute: MaybeUninit<i8>,
+ /// Seconds within the minute of the UTC offset.
+ offset_second: MaybeUninit<i8>,
+}
+
+#[allow(clippy::missing_docs_in_private_items)]
+impl Parsed {
+ const YEAR_FLAG: u16 = 1 << 0;
+ const YEAR_LAST_TWO_FLAG: u16 = 1 << 1;
+ const ISO_YEAR_FLAG: u16 = 1 << 2;
+ const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3;
+ const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4;
+ const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5;
+ const HOUR_24_FLAG: u16 = 1 << 6;
+ const MINUTE_FLAG: u16 = 1 << 7;
+ const SECOND_FLAG: u16 = 1 << 8;
+ const SUBSECOND_FLAG: u16 = 1 << 9;
+ const OFFSET_HOUR_FLAG: u16 = 1 << 10;
+ const OFFSET_MINUTE_FLAG: u16 = 1 << 11;
+ const OFFSET_SECOND_FLAG: u16 = 1 << 12;
+ /// Indicates whether a leap second is permitted to be parsed. This is required by some
+ /// well-known formats.
+ const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13;
+}
+
+impl Parsed {
+ /// Create a new instance of `Parsed` with no information known.
+ pub const fn new() -> Self {
+ Self {
+ flags: 0,
+ year: MaybeUninit::uninit(),
+ year_last_two: MaybeUninit::uninit(),
+ iso_year: MaybeUninit::uninit(),
+ iso_year_last_two: MaybeUninit::uninit(),
+ month: None,
+ sunday_week_number: MaybeUninit::uninit(),
+ monday_week_number: MaybeUninit::uninit(),
+ iso_week_number: None,
+ weekday: None,
+ ordinal: None,
+ day: None,
+ hour_24: MaybeUninit::uninit(),
+ hour_12: None,
+ hour_12_is_pm: None,
+ minute: MaybeUninit::uninit(),
+ second: MaybeUninit::uninit(),
+ subsecond: MaybeUninit::uninit(),
+ offset_hour: MaybeUninit::uninit(),
+ offset_minute: MaybeUninit::uninit(),
+ offset_second: MaybeUninit::uninit(),
+ }
+ }
+
+ /// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining
+ /// input is returned as the `Ok` value.
+ ///
+ /// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not
+ /// fail; the input will be returned as-is if the expected format is not present.
+ pub fn parse_item<'a>(
+ &mut self,
+ input: &'a [u8],
+ item: &impl sealed::AnyFormatItem,
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ item.parse_item(self, input)
+ }
+
+ /// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The
+ /// remaining input is returned as the `Ok` value.
+ ///
+ /// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail
+ /// to parse. `self` will not be mutated in this instance.
+ pub fn parse_items<'a>(
+ &mut self,
+ mut input: &'a [u8],
+ items: &[impl sealed::AnyFormatItem],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ // Make a copy that we can mutate. It will only be set to the user's copy if everything
+ // succeeds.
+ let mut this = *self;
+ for item in items {
+ input = this.parse_item(input, item)?;
+ }
+ *self = this;
+ Ok(input)
+ }
+
+ /// Parse a literal byte sequence. The remaining input is returned as the `Ok` value.
+ pub fn parse_literal<'a>(
+ input: &'a [u8],
+ literal: &[u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ input
+ .strip_prefix(literal)
+ .ok_or(error::ParseFromDescription::InvalidLiteral)
+ }
+
+ /// Parse a single component, mutating the struct. The remaining input is returned as the `Ok`
+ /// value.
+ pub fn parse_component<'a>(
+ &mut self,
+ input: &'a [u8],
+ component: Component,
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ use error::ParseFromDescription::InvalidComponent;
+
+ match component {
+ Component::Day(modifiers) => parse_day(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_day(value)))
+ .ok_or(InvalidComponent("day")),
+ Component::Month(modifiers) => parse_month(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_month(value)))
+ .ok_or(InvalidComponent("month")),
+ Component::Ordinal(modifiers) => parse_ordinal(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_ordinal(value)))
+ .ok_or(InvalidComponent("ordinal")),
+ Component::Weekday(modifiers) => parse_weekday(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_weekday(value)))
+ .ok_or(InvalidComponent("weekday")),
+ Component::WeekNumber(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?;
+ match modifiers.repr {
+ WeekNumberRepr::Iso => {
+ NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value))
+ }
+ WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
+ WeekNumberRepr::Monday => self.set_monday_week_number(value),
+ }
+ .ok_or(InvalidComponent("week number"))?;
+ Ok(remaining)
+ }
+ Component::Year(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_year(input, modifiers).ok_or(InvalidComponent("year"))?;
+ match (modifiers.iso_week_based, modifiers.repr) {
+ (false, YearRepr::Full) => self.set_year(value),
+ (false, YearRepr::LastTwo) => self.set_year_last_two(value as _),
+ (true, YearRepr::Full) => self.set_iso_year(value),
+ (true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _),
+ }
+ .ok_or(InvalidComponent("year"))?;
+ Ok(remaining)
+ }
+ Component::Hour(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_hour(input, modifiers).ok_or(InvalidComponent("hour"))?;
+ if modifiers.is_12_hour_clock {
+ NonZeroU8::new(value).and_then(|value| self.set_hour_12(value))
+ } else {
+ self.set_hour_24(value)
+ }
+ .ok_or(InvalidComponent("hour"))?;
+ Ok(remaining)
+ }
+ Component::Minute(modifiers) => parse_minute(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_minute(value)))
+ .ok_or(InvalidComponent("minute")),
+ Component::Period(modifiers) => parse_period(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_hour_12_is_pm(value == Period::Pm))
+ })
+ .ok_or(InvalidComponent("period")),
+ Component::Second(modifiers) => parse_second(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_second(value)))
+ .ok_or(InvalidComponent("second")),
+ Component::Subsecond(modifiers) => parse_subsecond(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_subsecond(value)))
+ .ok_or(InvalidComponent("subsecond")),
+ Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_offset_hour(value)))
+ .ok_or(InvalidComponent("offset hour")),
+ Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_offset_minute_signed(value))
+ })
+ .ok_or(InvalidComponent("offset minute")),
+ Component::OffsetSecond(modifiers) => parse_offset_second(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_offset_second_signed(value))
+ })
+ .ok_or(InvalidComponent("offset second")),
+ }
+ }
+}
+
+/// Generate getters for each of the fields.
+macro_rules! getters {
+ ($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
+ getters!(! $(@$flag)? $name: $ty);
+ )*};
+ (! $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ self.$name
+ }
+ };
+ (! @$flag:ident $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ if self.flags & Self::$flag != Self::$flag {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.$name.assume_init() })
+ }
+ }
+ };
+}
+
+/// Getter methods
+impl Parsed {
+ getters! {
+ @YEAR_FLAG year: i32,
+ @YEAR_LAST_TWO_FLAG year_last_two: u8,
+ @ISO_YEAR_FLAG iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
+ month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
+ iso_week_number: NonZeroU8,
+ weekday: Weekday,
+ ordinal: NonZeroU16,
+ day: NonZeroU8,
+ @HOUR_24_FLAG hour_24: u8,
+ hour_12: NonZeroU8,
+ hour_12_is_pm: bool,
+ @MINUTE_FLAG minute: u8,
+ @SECOND_FLAG second: u8,
+ @SUBSECOND_FLAG subsecond: u32,
+ @OFFSET_HOUR_FLAG offset_hour: i8,
+ }
+
+ /// Obtain the absolute value of the offset minute.
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")]
+ pub const fn offset_minute(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs())
+ }
+
+ /// Obtain the offset minute as an `i8`.
+ pub const fn offset_minute_signed(&self) -> Option<i8> {
+ if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.offset_minute.assume_init() })
+ }
+ }
+
+ /// Obtain the absolute value of the offset second.
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")]
+ pub const fn offset_second(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_second_signed()).unsigned_abs())
+ }
+
+ /// Obtain the offset second as an `i8`.
+ pub const fn offset_second_signed(&self) -> Option<i8> {
+ if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.offset_second.assume_init() })
+ }
+ }
+
+ /// Obtain whether leap seconds are permitted in the current format.
+ pub(crate) const fn leap_second_allowed(&self) -> bool {
+ self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG
+ }
+}
+
+/// Generate setters for each of the fields.
+///
+/// This macro should only be used for fields where the value is not validated beyond its type.
+macro_rules! setters {
+ ($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
+ setters!(! $(@$flag)? $setter_name $name: $ty);
+ )*};
+ (! $setter_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component.
+ pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
+ self.$name = Some(value);
+ Some(())
+ }
+ };
+ (! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component.
+ pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(())
+ }
+ };
+}
+
+/// Setter methods
+///
+/// All setters return `Option<()>`, which is `Some` if the value was set, and `None` if not. The
+/// setters _may_ fail if the value is invalid, though behavior is not guaranteed.
+impl Parsed {
+ setters! {
+ @YEAR_FLAG set_year year: i32,
+ @YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8,
+ @ISO_YEAR_FLAG set_iso_year iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8,
+ set_month month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8,
+ set_iso_week_number iso_week_number: NonZeroU8,
+ set_weekday weekday: Weekday,
+ set_ordinal ordinal: NonZeroU16,
+ set_day day: NonZeroU8,
+ @HOUR_24_FLAG set_hour_24 hour_24: u8,
+ set_hour_12 hour_12: NonZeroU8,
+ set_hour_12_is_pm hour_12_is_pm: bool,
+ @MINUTE_FLAG set_minute minute: u8,
+ @SECOND_FLAG set_second second: u8,
+ @SUBSECOND_FLAG set_subsecond subsecond: u32,
+ @OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8,
+ }
+
+ /// Set the named component.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_minute_signed()` instead"
+ )]
+ pub fn set_offset_minute(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.set_offset_minute_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_minute` component.
+ pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> {
+ self.offset_minute = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_MINUTE_FLAG;
+ Some(())
+ }
+
+ /// Set the named component.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_second_signed()` instead"
+ )]
+ pub fn set_offset_second(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.set_offset_second_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_second` component.
+ pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> {
+ self.offset_second = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_SECOND_FLAG;
+ Some(())
+ }
+
+ /// Set the leap second allowed flag.
+ pub(crate) fn set_leap_second_allowed(&mut self, value: bool) {
+ if value {
+ self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG;
+ } else {
+ self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG;
+ }
+ }
+}
+
+/// Generate build methods for each of the fields.
+///
+/// This macro should only be used for fields where the value is not validated beyond its type.
+macro_rules! builders {
+ ($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
+ builders!(! $(@$flag)? $builder_name $name: $ty);
+ )*};
+ (! $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = Some(value);
+ Some(self)
+ }
+ };
+ (! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(self)
+ }
+ };
+}
+
+/// Builder methods
+///
+/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if
+/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
+impl Parsed {
+ builders! {
+ @YEAR_FLAG with_year year: i32,
+ @YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
+ @ISO_YEAR_FLAG with_iso_year iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
+ with_month month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
+ with_iso_week_number iso_week_number: NonZeroU8,
+ with_weekday weekday: Weekday,
+ with_ordinal ordinal: NonZeroU16,
+ with_day day: NonZeroU8,
+ @HOUR_24_FLAG with_hour_24 hour_24: u8,
+ with_hour_12 hour_12: NonZeroU8,
+ with_hour_12_is_pm hour_12_is_pm: bool,
+ @MINUTE_FLAG with_minute minute: u8,
+ @SECOND_FLAG with_second second: u8,
+ @SUBSECOND_FLAG with_subsecond subsecond: u32,
+ @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
+ }
+
+ /// Set the named component and return `self`.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.with_offset_minute_signed()` instead"
+ )]
+ pub const fn with_offset_minute(self, value: u8) -> Option<Self> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.with_offset_minute_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_minute` component and return `self`.
+ pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
+ self.offset_minute = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_MINUTE_FLAG;
+ Some(self)
+ }
+
+ /// Set the named component and return `self`.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.with_offset_second_signed()` instead"
+ )]
+ pub const fn with_offset_second(self, value: u8) -> Option<Self> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.with_offset_second_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_second` component and return `self`.
+ pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
+ self.offset_second = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_SECOND_FLAG;
+ Some(self)
+ }
+}
+
+impl TryFrom<Parsed> for Date {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ /// Match on the components that need to be present.
+ macro_rules! match_ {
+ (_ => $catch_all:expr $(,)?) => {
+ $catch_all
+ };
+ (($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => {
+ if let ($(Some($name)),*) = ($(parsed.$name()),*) {
+ $arm
+ } else {
+ match_!($($rest)*)
+ }
+ };
+ }
+
+ /// Get the value needed to adjust the ordinal day for Sunday and Monday-based week
+ /// numbering.
+ const fn adjustment(year: i32) -> i16 {
+ match Date::__from_ordinal_date_unchecked(year, 1).weekday() {
+ Weekday::Monday => 7,
+ Weekday::Tuesday => 1,
+ Weekday::Wednesday => 2,
+ Weekday::Thursday => 3,
+ Weekday::Friday => 4,
+ Weekday::Saturday => 5,
+ Weekday::Sunday => 6,
+ }
+ }
+
+ // TODO Only the basics have been covered. There are many other valid values that are not
+ // currently constructed from the information known.
+
+ match_! {
+ (year, ordinal) => Ok(Self::from_ordinal_date(year, ordinal.get())?),
+ (year, month, day) => Ok(Self::from_calendar_date(year, month, day.get())?),
+ (iso_year, iso_week_number, weekday) => Ok(Self::from_iso_week_date(
+ iso_year,
+ iso_week_number.get(),
+ weekday,
+ )?),
+ (year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date(
+ year,
+ (sunday_week_number as i16 * 7 + weekday.number_days_from_sunday() as i16
+ - adjustment(year)
+ + 1) as u16,
+ )?),
+ (year, monday_week_number, weekday) => Ok(Self::from_ordinal_date(
+ year,
+ (monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16
+ - adjustment(year)
+ + 1) as u16,
+ )?),
+ _ => Err(InsufficientInformation),
+ }
+ }
+}
+
+impl TryFrom<Parsed> for Time {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ let hour = match (parsed.hour_24(), parsed.hour_12(), parsed.hour_12_is_pm()) {
+ (Some(hour), _, _) => hour,
+ (_, Some(hour), Some(false)) if hour.get() == 12 => 0,
+ (_, Some(hour), Some(true)) if hour.get() == 12 => 12,
+ (_, Some(hour), Some(false)) => hour.get(),
+ (_, Some(hour), Some(true)) => hour.get() + 12,
+ _ => return Err(InsufficientInformation),
+ };
+ if parsed.hour_24().is_none()
+ && parsed.hour_12().is_some()
+ && parsed.hour_12_is_pm().is_some()
+ && parsed.minute().is_none()
+ && parsed.second().is_none()
+ && parsed.subsecond().is_none()
+ {
+ return Ok(Self::from_hms_nano(hour, 0, 0, 0)?);
+ }
+ let minute = parsed.minute().ok_or(InsufficientInformation)?;
+ let second = parsed.second().unwrap_or(0);
+ let subsecond = parsed.subsecond().unwrap_or(0);
+ Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
+ }
+}
+
+impl TryFrom<Parsed> for UtcOffset {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ let hour = parsed.offset_hour().ok_or(InsufficientInformation)?;
+ let minute = parsed.offset_minute_signed().unwrap_or(0);
+ let second = parsed.offset_second_signed().unwrap_or(0);
+
+ Self::from_hms(hour, minute, second).map_err(|mut err| {
+ // Provide the user a more accurate error.
+ if err.name == "hours" {
+ err.name = "offset hour";
+ } else if err.name == "minutes" {
+ err.name = "offset minute";
+ } else if err.name == "seconds" {
+ err.name = "offset second";
+ }
+ err.into()
+ })
+ }
+}
+
+impl TryFrom<Parsed> for PrimitiveDateTime {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ Ok(Self::new(parsed.try_into()?, parsed.try_into()?))
+ }
+}
+
+impl TryFrom<Parsed> for OffsetDateTime {
+ type Error = error::TryFromParsed;
+
+ #[allow(clippy::unwrap_in_result)] // We know the values are valid.
+ fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
+ // Some well-known formats explicitly allow leap seconds. We don't currently support them,
+ // so treat it as the nearest preceding moment that can be represented. Because leap seconds
+ // always fall at the end of a month UTC, reject any that are at other times.
+ let leap_second_input = if parsed.leap_second_allowed() && parsed.second() == Some(60) {
+ parsed.set_second(59).expect("59 is a valid second");
+ parsed
+ .set_subsecond(999_999_999)
+ .expect("999_999_999 is a valid subsecond");
+ true
+ } else {
+ false
+ };
+ let dt = PrimitiveDateTime::try_from(parsed)?.assume_offset(parsed.try_into()?);
+ if leap_second_input && !dt.is_valid_leap_second_stand_in() {
+ return Err(error::TryFromParsed::ComponentRange(
+ error::ComponentRange {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ ));
+ }
+ Ok(dt)
+ }
+}
diff --git a/third_party/rust/time/src/parsing/shim.rs b/third_party/rust/time/src/parsing/shim.rs
new file mode 100644
index 0000000000..00aaf4852b
--- /dev/null
+++ b/third_party/rust/time/src/parsing/shim.rs
@@ -0,0 +1,50 @@
+//! Extension traits for things either not implemented or not yet stable in the MSRV.
+
+/// Equivalent of `foo.parse()` for slices.
+pub(crate) trait IntegerParseBytes<T> {
+ #[allow(clippy::missing_docs_in_private_items)]
+ fn parse_bytes(&self) -> Option<T>;
+}
+
+impl<T: Integer> IntegerParseBytes<T> for [u8] {
+ fn parse_bytes(&self) -> Option<T> {
+ T::parse_bytes(self)
+ }
+}
+
+/// Marker trait for all integer types, including `NonZero*`
+pub(crate) trait Integer: Sized {
+ #[allow(clippy::missing_docs_in_private_items)]
+ fn parse_bytes(src: &[u8]) -> Option<Self>;
+}
+
+/// Parse the given types from bytes.
+macro_rules! impl_parse_bytes {
+ ($($t:ty)*) => ($(
+ impl Integer for $t {
+ #[allow(trivial_numeric_casts)]
+ fn parse_bytes(src: &[u8]) -> Option<Self> {
+ src.iter().try_fold::<Self, _, _>(0, |result, c| {
+ result.checked_mul(10)?.checked_add((c - b'0') as Self)
+ })
+ }
+ }
+ )*)
+}
+impl_parse_bytes! { u8 u16 u32 }
+
+/// Parse the given types from bytes.
+macro_rules! impl_parse_bytes_nonzero {
+ ($($t:ty)*) => {$(
+ impl Integer for $t {
+ fn parse_bytes(src: &[u8]) -> Option<Self> {
+ Self::new(src.parse_bytes()?)
+ }
+ }
+ )*}
+}
+
+impl_parse_bytes_nonzero! {
+ core::num::NonZeroU8
+ core::num::NonZeroU16
+}
diff --git a/third_party/rust/time/src/primitive_date_time.rs b/third_party/rust/time/src/primitive_date_time.rs
new file mode 100644
index 0000000000..6e842092ab
--- /dev/null
+++ b/third_party/rust/time/src/primitive_date_time.rs
@@ -0,0 +1,937 @@
+//! The [`PrimitiveDateTime`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
+
+/// Combined date and time.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct PrimitiveDateTime {
+ #[allow(clippy::missing_docs_in_private_items)]
+ pub(crate) date: Date,
+ #[allow(clippy::missing_docs_in_private_items)]
+ pub(crate) time: Time,
+}
+
+impl PrimitiveDateTime {
+ /// The smallest value that can be represented by `PrimitiveDateTime`.
+ ///
+ /// Depending on `large-dates` feature flag, value of this constant may vary.
+ ///
+ /// 1. With `large-dates` disabled it is equal to `-9999-01-01 00:00:00.0`
+ /// 2. With `large-dates` enabled it is equal to `-999999-01-01 00:00:00.0`
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::datetime;
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "// Assuming `large-dates` feature is enabled."
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-999999-01-01 0:00));"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "// Assuming `large-dates` feature is disabled."
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));"
+ )]
+ /// ```
+ pub const MIN: Self = Self::new(Date::MIN, Time::MIN);
+
+ /// The largest value that can be represented by `PrimitiveDateTime`.
+ ///
+ /// Depending on `large-dates` feature flag, value of this constant may vary.
+ ///
+ /// 1. With `large-dates` disabled it is equal to `9999-12-31 23:59:59.999_999_999`
+ /// 2. With `large-dates` enabled it is equal to `999999-12-31 23:59:59.999_999_999`
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::datetime;
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "// Assuming `large-dates` feature is enabled."
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+999999-12-31 23:59:59.999_999_999));"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "// Assuming `large-dates` feature is disabled."
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));"
+ )]
+ /// ```
+ pub const MAX: Self = Self::new(Date::MAX, Time::MAX);
+
+ /// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`].
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::{date, datetime, time};
+ /// assert_eq!(
+ /// PrimitiveDateTime::new(date!(2019-01-01), time!(0:00)),
+ /// datetime!(2019-01-01 0:00),
+ /// );
+ /// ```
+ pub const fn new(date: Date, time: Time) -> Self {
+ Self { date, time }
+ }
+
+ // region: component getters
+ /// Get the [`Date`] component of the `PrimitiveDateTime`.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime};
+ /// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01));
+ /// ```
+ pub const fn date(self) -> Date {
+ self.date
+ }
+
+ /// Get the [`Time`] component of the `PrimitiveDateTime`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, time};
+ /// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00));
+ pub const fn time(self) -> Time {
+ self.time
+ }
+ // endregion component getters
+
+ // region: date getters
+ /// Get the year of the date.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).year(), 2019);
+ /// assert_eq!(datetime!(2019-12-31 0:00).year(), 2019);
+ /// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.date.year()
+ }
+
+ /// Get the month of the date.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).month(), Month::January);
+ /// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December);
+ /// ```
+ pub const fn month(self) -> Month {
+ self.date.month()
+ }
+
+ /// Get the day of the date.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).day(), 1);
+ /// assert_eq!(datetime!(2019-12-31 0:00).day(), 31);
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.date.day()
+ }
+
+ /// Get the day of the year.
+ ///
+ /// The returned value will always be in the range `1..=366` (`1..=365` for common years).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).ordinal(), 1);
+ /// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365);
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ self.date.ordinal()
+ }
+
+ /// Get the ISO week number.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).iso_week(), 1);
+ /// assert_eq!(datetime!(2019-10-04 0:00).iso_week(), 40);
+ /// assert_eq!(datetime!(2020-01-01 0:00).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-12-31 0:00).iso_week(), 53);
+ /// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.date.iso_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00).sunday_based_week(), 52);
+ /// assert_eq!(datetime!(2021-01-01 0:00).sunday_based_week(), 0);
+ /// ```
+ pub const fn sunday_based_week(self) -> u8 {
+ self.date.sunday_based_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00).monday_based_week(), 52);
+ /// assert_eq!(datetime!(2021-01-01 0:00).monday_based_week(), 0);
+ /// ```
+ pub const fn monday_based_week(self) -> u8 {
+ self.date.monday_based_week()
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ self.date.to_calendar_date()
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1));
+ /// ```
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ self.date.to_ordinal_date()
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).to_iso_week_date(),
+ /// (2019, 1, Tuesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-10-04 0:00).to_iso_week_date(),
+ /// (2019, 40, Friday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-12-31 0:00).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2021-01-01 0:00).to_iso_week_date(),
+ /// (2020, 53, Friday)
+ /// );
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ self.date.to_iso_week_date()
+ }
+
+ /// Get the weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-02-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-03-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-04-01 0:00).weekday(), Monday);
+ /// assert_eq!(datetime!(2019-05-01 0:00).weekday(), Wednesday);
+ /// assert_eq!(datetime!(2019-06-01 0:00).weekday(), Saturday);
+ /// assert_eq!(datetime!(2019-07-01 0:00).weekday(), Monday);
+ /// assert_eq!(datetime!(2019-08-01 0:00).weekday(), Thursday);
+ /// assert_eq!(datetime!(2019-09-01 0:00).weekday(), Sunday);
+ /// assert_eq!(datetime!(2019-10-01 0:00).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-11-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ self.date.weekday()
+ }
+
+ /// Get the Julian day for the date. The time is not taken into account for this calculation.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(-4713-11-24 0:00).to_julian_day(), 0);
+ /// assert_eq!(datetime!(2000-01-01 0:00).to_julian_day(), 2_451_545);
+ /// assert_eq!(datetime!(2019-01-01 0:00).to_julian_day(), 2_458_485);
+ /// assert_eq!(datetime!(2019-12-31 0:00).to_julian_day(), 2_458_849);
+ /// ```
+ pub const fn to_julian_day(self) -> i32 {
+ self.date.to_julian_day()
+ }
+ // endregion date getters
+
+ // region: time getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms(), (0, 0, 0));
+ /// assert_eq!(datetime!(2020-01-01 23:59:59).as_hms(), (23, 59, 59));
+ /// ```
+ pub const fn as_hms(self) -> (u8, u8, u8) {
+ self.time.as_hms()
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_milli(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999).as_hms_milli(),
+ /// (23, 59, 59, 999)
+ /// );
+ /// ```
+ pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
+ self.time.as_hms_milli()
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_micro(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999).as_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_micro()
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_nano(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999_999).as_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_nano()
+ }
+
+ /// Get the clock hour.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).hour(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23);
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.time.hour()
+ }
+
+ /// Get the minute within the hour.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).minute(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59);
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.time.minute()
+ }
+
+ /// Get the second within the minute.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).second(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59);
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.time.second()
+ }
+
+ /// Get the milliseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).millisecond(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ self.time.millisecond()
+ }
+
+ /// Get the microseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).microsecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999).microsecond(),
+ /// 999_999
+ /// );
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.time.microsecond()
+ }
+
+ /// Get the nanoseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).nanosecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999_999).nanosecond(),
+ /// 999_999_999,
+ /// );
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.time.nanosecond()
+ }
+ // endregion time getters
+
+ // region: attach offset
+ /// Assuming that the existing `PrimitiveDateTime` represents a moment in the provided
+ /// [`UtcOffset`], return an [`OffsetDateTime`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00)
+ /// .assume_offset(offset!(UTC))
+ /// .unix_timestamp(),
+ /// 1_546_300_800,
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00)
+ /// .assume_offset(offset!(-1))
+ /// .unix_timestamp(),
+ /// 1_546_304_400,
+ /// );
+ /// ```
+ pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime {
+ OffsetDateTime {
+ local_datetime: self,
+ offset,
+ }
+ }
+
+ /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an
+ /// [`OffsetDateTime`].
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).assume_utc().unix_timestamp(),
+ /// 1_546_300_800,
+ /// );
+ /// ```
+ pub const fn assume_utc(self) -> OffsetDateTime {
+ self.assume_offset(UtcOffset::UTC)
+ }
+ // endregion attach offset
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// let datetime = Date::MIN.midnight();
+ /// assert_eq!(datetime.checked_add((-2).days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight();
+ /// assert_eq!(datetime.checked_add(1.days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).checked_add(27.hours()),
+ /// Some(datetime!(2019 - 11 - 26 18:30))
+ /// );
+ /// ```
+ pub const fn checked_add(self, duration: Duration) -> Option<Self> {
+ let (date_adjustment, time) = self.time.adjusting_add(duration);
+ let date = const_try_opt!(self.date.checked_add(duration));
+
+ Some(Self {
+ date: match date_adjustment {
+ util::DateAdjustment::Previous => const_try_opt!(date.previous_day()),
+ util::DateAdjustment::Next => const_try_opt!(date.next_day()),
+ util::DateAdjustment::None => date,
+ },
+ time,
+ })
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// let datetime = Date::MIN.midnight();
+ /// assert_eq!(datetime.checked_sub(2.days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight();
+ /// assert_eq!(datetime.checked_sub((-1).days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).checked_sub(27.hours()),
+ /// Some(datetime!(2019 - 11 - 24 12:30))
+ /// );
+ /// ```
+ pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
+ let (date_adjustment, time) = self.time.adjusting_sub(duration);
+ let date = const_try_opt!(self.date.checked_sub(duration));
+
+ Some(Self {
+ date: match date_adjustment {
+ util::DateAdjustment::Previous => const_try_opt!(date.previous_day()),
+ util::DateAdjustment::Next => const_try_opt!(date.next_day()),
+ util::DateAdjustment::None => date,
+ },
+ time,
+ })
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{PrimitiveDateTime, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// PrimitiveDateTime::MIN.saturating_add((-2).days()),
+ /// PrimitiveDateTime::MIN
+ /// );
+ ///
+ /// assert_eq!(
+ /// PrimitiveDateTime::MAX.saturating_add(2.days()),
+ /// PrimitiveDateTime::MAX
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).saturating_add(27.hours()),
+ /// datetime!(2019 - 11 - 26 18:30)
+ /// );
+ /// ```
+ pub const fn saturating_add(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_add(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self::MIN
+ } else {
+ Self::MAX
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{PrimitiveDateTime, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// PrimitiveDateTime::MIN.saturating_sub(2.days()),
+ /// PrimitiveDateTime::MIN
+ /// );
+ ///
+ /// assert_eq!(
+ /// PrimitiveDateTime::MAX.saturating_sub((-2).days()),
+ /// PrimitiveDateTime::MAX
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).saturating_sub(27.hours()),
+ /// datetime!(2019 - 11 - 24 12:30)
+ /// );
+ /// ```
+ pub const fn saturating_sub(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_sub(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self::MAX
+ } else {
+ Self::MIN
+ }
+ }
+ // endregion: saturating arithmetic
+}
+
+// region: replacement
+/// Methods that replace part of the `PrimitiveDateTime`.
+impl PrimitiveDateTime {
+ /// Replace the time, preserving the date.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, time};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 17:00).replace_time(time!(5:00)),
+ /// datetime!(2020-01-01 5:00)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_time(self, time: Time) -> Self {
+ self.date.with_time(time)
+ }
+
+ /// Replace the date, preserving the time.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, date};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 12:00)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_date(self, date: Date) -> Self {
+ date.with_time(self.time)
+ }
+
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_year(2019),
+ /// Ok(datetime!(2019 - 02 - 18 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.date.replace_year(year)).with_time(self.time))
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_month(Month::January),
+ /// Ok(datetime!(2022 - 01 - 18 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 01 - 30 12:00).replace_month(Month::February).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.date.replace_month(month)).with_time(self.time))
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_day(1),
+ /// Ok(datetime!(2022 - 02 - 01 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(0).is_err()); // 00 isn't a valid day
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(30).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
+ Ok(const_try!(self.date.replace_day(day)).with_time(self.time))
+ }
+
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(7),
+ /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_hour(hour))))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_minute(minute))))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_second(second))))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_millisecond(millisecond))))
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(7_008),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_microsecond(microsecond))))
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(7_008_009),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_nanosecond(nanosecond))))
+ }
+}
+// endregion replacement
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl PrimitiveDateTime {
+ /// Format the `PrimitiveDateTime` using the provided [format
+ /// description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, error::Format> {
+ format.format_into(output, Some(self.date), Some(self.time), None)
+ }
+
+ /// Format the `PrimitiveDateTime` using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::datetime;
+ /// let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?;
+ /// assert_eq!(
+ /// datetime!(2020-01-02 03:04:05).format(&format)?,
+ /// "2020-01-02 03:04:05"
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(Some(self.date), Some(self.time), None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl PrimitiveDateTime {
+ /// Parse a `PrimitiveDateTime` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::{datetime, format_description};
+ /// let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
+ /// assert_eq!(
+ /// PrimitiveDateTime::parse("2020-01-02 03:04:05", &format)?,
+ /// datetime!(2020-01-02 03:04:05)
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_date_time(input.as_bytes())
+ }
+}
+
+impl fmt::Display for PrimitiveDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {}", self.date, self.time)
+ }
+}
+
+impl fmt::Debug for PrimitiveDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl Add<Duration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ self.checked_add(duration)
+ .expect("resulting value is out of range")
+ }
+}
+
+impl Add<StdDuration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ let (is_next_day, time) = self.time.adjusting_add_std(duration);
+
+ Self {
+ date: if is_next_day {
+ (self.date + duration)
+ .next_day()
+ .expect("resulting value is out of range")
+ } else {
+ self.date + duration
+ },
+ time,
+ }
+ }
+}
+
+impl_add_assign!(PrimitiveDateTime: Duration, StdDuration);
+
+impl Sub<Duration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ self.checked_sub(duration)
+ .expect("resulting value is out of range")
+ }
+}
+
+impl Sub<StdDuration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ let (is_previous_day, time) = self.time.adjusting_sub_std(duration);
+
+ Self {
+ date: if is_previous_day {
+ (self.date - duration)
+ .previous_day()
+ .expect("resulting value is out of range")
+ } else {
+ self.date - duration
+ },
+ time,
+ }
+ }
+}
+
+impl_sub_assign!(PrimitiveDateTime: Duration, StdDuration);
+
+impl Sub for PrimitiveDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ (self.date - rhs.date) + (self.time - rhs.time)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/quickcheck.rs b/third_party/rust/time/src/quickcheck.rs
new file mode 100644
index 0000000000..707f3e0a97
--- /dev/null
+++ b/third_party/rust/time/src/quickcheck.rs
@@ -0,0 +1,220 @@
+//! Implementations of the [`quickcheck::Arbitrary`](quickcheck::Arbitrary) trait.
+//!
+//! This enables users to write tests such as this, and have test values provided automatically:
+//!
+//! ```
+//! # #![allow(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 [`Duration`], and obtaining an `Instant` from a [`Duration`] is very simple
+//! anyway.
+
+use alloc::boxed::Box;
+
+use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen};
+
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, 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 {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::from_julian_day_unchecked(arbitrary_between!(
+ i32;
+ g,
+ Self::MIN.to_julian_day(),
+ Self::MAX.to_julian_day()
+ ))
+ }
+
+ 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 Duration {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::nanoseconds_i128(arbitrary_between!(
+ i128;
+ g,
+ Self::MIN.whole_nanoseconds(),
+ Self::MAX.whole_nanoseconds()
+ ))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.subsec_nanoseconds(), self.whole_seconds())
+ .shrink()
+ .map(|(mut nanoseconds, seconds)| {
+ // Coerce the sign if necessary.
+ if (seconds > 0 && nanoseconds < 0) || (seconds < 0 && nanoseconds > 0) {
+ nanoseconds *= -1;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for Time {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::__from_hms_nanos_unchecked(
+ arbitrary_between!(u8; g, 0, 23),
+ arbitrary_between!(u8; g, 0, 59),
+ arbitrary_between!(u8; g, 0, 59),
+ arbitrary_between!(u32; g, 0, 999_999_999),
+ )
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ self.as_hms_nano()
+ .shrink()
+ .map(|(hour, minute, second, nanosecond)| {
+ Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for PrimitiveDateTime {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::new(<_>::arbitrary(g), <_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.date, self.time)
+ .shrink()
+ .map(|(date, time)| Self { date, time }),
+ )
+ }
+}
+
+impl Arbitrary for UtcOffset {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let seconds = arbitrary_between!(i32; g, -86_399, 86_399);
+ Self::__from_hms_unchecked(
+ (seconds / 3600) as _,
+ ((seconds % 3600) / 60) as _,
+ (seconds % 60) as _,
+ )
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ self.as_hms().shrink().map(|(hours, minutes, seconds)| {
+ Self::__from_hms_unchecked(hours, minutes, seconds)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for OffsetDateTime {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let datetime = PrimitiveDateTime::arbitrary(g);
+ datetime.assume_offset(<_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.local_datetime, self.offset)
+ .shrink()
+ .map(|(local_datetime, offset)| local_datetime.assume_offset(offset)),
+ )
+ }
+}
+
+impl Arbitrary for Weekday {
+ 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
+ }
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ match self {
+ Self::Monday => empty_shrinker(),
+ _ => single_shrinker(self.previous()),
+ }
+ }
+}
+
+impl Arbitrary for Month {
+ 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
+ }
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ match self {
+ Self::January => empty_shrinker(),
+ _ => single_shrinker(self.previous()),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/rand.rs b/third_party/rust/time/src/rand.rs
new file mode 100644
index 0000000000..8afefe5075
--- /dev/null
+++ b/third_party/rust/time/src/rand.rs
@@ -0,0 +1,99 @@
+//! Implementation of [`Distribution`] for various structs.
+
+use rand::distributions::{Distribution, Standard};
+use rand::Rng;
+
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+impl Distribution<Time> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Time {
+ Time::__from_hms_nanos_unchecked(
+ rng.gen_range(0..24),
+ rng.gen_range(0..60),
+ rng.gen_range(0..60),
+ rng.gen_range(0..1_000_000_000),
+ )
+ }
+}
+
+impl Distribution<Date> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Date {
+ Date::from_julian_day_unchecked(
+ rng.gen_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()),
+ )
+ }
+}
+
+impl Distribution<UtcOffset> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UtcOffset {
+ let seconds = rng.gen_range(-86399..=86399);
+ UtcOffset::__from_hms_unchecked(
+ (seconds / 3600) as _,
+ ((seconds % 3600) / 60) as _,
+ (seconds % 60) as _,
+ )
+ }
+}
+
+impl Distribution<PrimitiveDateTime> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(Self.sample(rng), Self.sample(rng))
+ }
+}
+
+impl Distribution<OffsetDateTime> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> OffsetDateTime {
+ let date_time: PrimitiveDateTime = Self.sample(rng);
+ date_time.assume_offset(Self.sample(rng))
+ }
+}
+
+impl Distribution<Duration> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Duration {
+ Duration::nanoseconds_i128(
+ rng.gen_range(Duration::MIN.whole_nanoseconds()..=Duration::MAX.whole_nanoseconds()),
+ )
+ }
+}
+
+impl Distribution<Weekday> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Weekday {
+ 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 {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Month {
+ 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
+ }
+ }
+ }
+}
diff --git a/third_party/rust/time/src/serde/iso8601.rs b/third_party/rust/time/src/serde/iso8601.rs
new file mode 100644
index 0000000000..75deb62f1a
--- /dev/null
+++ b/third_party/rust/time/src/serde/iso8601.rs
@@ -0,0 +1,77 @@
+//! Use the well-known [ISO 8601 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::iso8601::{Config, EncodedConfig};
+use crate::format_description::well_known::Iso8601;
+use crate::OffsetDateTime;
+
+/// The configuration of ISO 8601 used for serde implementations.
+pub(crate) const SERDE_CONFIG: EncodedConfig =
+ Config::DEFAULT.set_year_is_six_digits(true).encode();
+
+/// Serialize an [`OffsetDateTime`] using the well-known ISO 8601 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Iso8601::<SERDE_CONFIG>)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+}
+
+/// Deserialize an [`OffsetDateTime`] from its ISO 8601 representation.
+#[cfg(feature = "parsing")]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ deserializer.deserialize_any(Visitor::<Iso8601<SERDE_CONFIG>>(PhantomData))
+}
+
+/// Use the well-known ISO 8601 format when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known ISO 8601 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Iso8601::<SERDE_CONFIG>))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its ISO 8601 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Iso8601<SERDE_CONFIG>>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/mod.rs b/third_party/rust/time/src/serde/mod.rs
new file mode 100644
index 0000000000..e9f6d4394c
--- /dev/null
+++ b/third_party/rust/time/src/serde/mod.rs
@@ -0,0 +1,375 @@
+//! Differential formats for serde.
+// This also includes the serde implementations for all types. This doesn't need to be externally
+// documented, though.
+
+// Types with guaranteed stable serde representations. Strings are avoided to allow for optimal
+// representations in various binary forms.
+
+/// Consume the next item in a sequence.
+macro_rules! item {
+ ($seq:expr, $name:literal) => {
+ $seq.next_element()?
+ .ok_or_else(|| <A::Error as serde::de::Error>::custom(concat!("expected ", $name)))
+ };
+}
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod iso8601;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod rfc2822;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod rfc3339;
+pub mod timestamp;
+mod visitor;
+
+use core::marker::PhantomData;
+
+#[cfg(feature = "serde-human-readable")]
+use serde::ser::Error as _;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+/// Generate a custom serializer and deserializer from the provided string.
+///
+/// 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).
+///
+/// # Usage
+///
+/// Invoked as `serde::format_description!(mod_name, Date, "<format string>")`. This puts a
+/// module named `mod_name` in the current scope that can be used to format `Date` structs. A
+/// submodule (`mod_name::option`) is also generated for `Option<Date>`. Both modules are only
+/// visible in the current scope.
+///
+/// The returned `Option` will contain a deserialized value if present and `None` if the field
+/// is present but the value is `null` (or the equivalent in other formats). To return `None`
+/// when the field is not present, you should use `#[serde(default)]` on the field.
+///
+/// # Examples
+///
+/// ```rust,no_run
+/// # use time::OffsetDateTime;
+#[cfg_attr(
+ all(feature = "formatting", feature = "parsing"),
+ doc = "use ::serde::{Serialize, Deserialize};"
+)]
+#[cfg_attr(
+ all(feature = "formatting", not(feature = "parsing")),
+ doc = "use ::serde::Serialize;"
+)]
+#[cfg_attr(
+ all(not(feature = "formatting"), feature = "parsing"),
+ doc = "use ::serde::Deserialize;"
+)]
+/// use time::serde;
+///
+/// // Makes a module `mod my_format { ... }`.
+/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]");
+#[cfg_attr(
+ all(feature = "formatting", feature = "parsing"),
+ doc = "#[derive(Serialize, Deserialize)]"
+)]
+#[cfg_attr(
+ all(feature = "formatting", not(feature = "parsing")),
+ doc = "#[derive(Serialize)]"
+)]
+#[cfg_attr(
+ all(not(feature = "formatting"), feature = "parsing"),
+ doc = "#[derive(Deserialize)]"
+)]
+/// # #[allow(dead_code)]
+/// struct SerializesWithCustom {
+/// #[serde(with = "my_format")]
+/// dt: OffsetDateTime,
+/// #[serde(with = "my_format::option")]
+/// maybe_dt: Option<OffsetDateTime>,
+/// }
+/// ```
+///
+/// [`format_description::parse()`]: crate::format_description::parse()
+#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))]
+pub use time_macros::serde_format_description as format_description;
+
+use self::visitor::Visitor;
+#[cfg(feature = "parsing")]
+use crate::format_description::{modifier, Component, FormatItem};
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+// region: Date
+/// The format used when serializing and deserializing a human-readable `Date`.
+#[cfg(feature = "parsing")]
+const DATE_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::Year(modifier::Year::default())),
+ FormatItem::Literal(b"-"),
+ FormatItem::Component(Component::Month(modifier::Month::default())),
+ FormatItem::Literal(b"-"),
+ FormatItem::Component(Component::Day(modifier::Day::default())),
+];
+
+impl Serialize for Date {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.serialize_str(&match self.format(&DATE_FORMAT) {
+ Ok(s) => s,
+ Err(_) => return Err(S::Error::custom("failed formatting `Date`")),
+ });
+ }
+
+ (self.year(), self.ordinal()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Date {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion date
+
+// region: Duration
+impl Serialize for Duration {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.collect_str(&format_args!(
+ "{}.{:>09}",
+ self.whole_seconds(),
+ self.subsec_nanoseconds().abs()
+ ));
+ }
+
+ (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Duration {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Duration
+
+// region: OffsetDateTime
+/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
+#[cfg(feature = "parsing")]
+const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Compound(DATE_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(TIME_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(UTC_OFFSET_FORMAT),
+];
+
+impl Serialize for OffsetDateTime {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.serialize_str(&match self.format(&OFFSET_DATE_TIME_FORMAT) {
+ Ok(s) => s,
+ Err(_) => return Err(S::Error::custom("failed formatting `OffsetDateTime`")),
+ });
+ }
+
+ (
+ self.year(),
+ self.ordinal(),
+ self.hour(),
+ self.minute(),
+ self.second(),
+ self.nanosecond(),
+ self.offset.whole_hours(),
+ self.offset.minutes_past_hour(),
+ self.offset.seconds_past_minute(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for OffsetDateTime {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion OffsetDateTime
+
+// region: PrimitiveDateTime
+/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
+#[cfg(feature = "parsing")]
+const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Compound(DATE_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(TIME_FORMAT),
+];
+
+impl Serialize for PrimitiveDateTime {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.serialize_str(&match self.format(&PRIMITIVE_DATE_TIME_FORMAT) {
+ Ok(s) => s,
+ Err(_) => return Err(<S::Error>::custom("failed formatting `PrimitiveDateTime`")),
+ });
+ }
+
+ (
+ self.year(),
+ self.ordinal(),
+ self.hour(),
+ self.minute(),
+ self.second(),
+ self.nanosecond(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for PrimitiveDateTime {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion PrimitiveDateTime
+
+// region: Time
+/// The format used when serializing and deserializing a human-readable `Time`.
+#[cfg(feature = "parsing")]
+const TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::Hour(<modifier::Hour>::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::Minute(<modifier::Minute>::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::Second(<modifier::Second>::default())),
+ FormatItem::Literal(b"."),
+ FormatItem::Component(Component::Subsecond(<modifier::Subsecond>::default())),
+];
+
+impl Serialize for Time {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.serialize_str(&match self.format(&TIME_FORMAT) {
+ Ok(s) => s,
+ Err(_) => return Err(S::Error::custom("failed formatting `Time`")),
+ });
+ }
+
+ (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Time {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Time
+
+// region: UtcOffset
+/// The format used when serializing and deserializing a human-readable `UtcOffset`.
+#[cfg(feature = "parsing")]
+const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::OffsetHour(modifier::OffsetHour::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetSecond(modifier::OffsetSecond::default())),
+];
+
+impl Serialize for UtcOffset {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.serialize_str(&match self.format(&UTC_OFFSET_FORMAT) {
+ Ok(s) => s,
+ Err(_) => return Err(S::Error::custom("failed formatting `UtcOffset`")),
+ });
+ }
+
+ (
+ self.whole_hours(),
+ self.minutes_past_hour(),
+ self.seconds_past_minute(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for UtcOffset {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion UtcOffset
+
+// region: Weekday
+impl Serialize for Weekday {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ #[cfg(not(feature = "std"))]
+ use alloc::string::ToString;
+ return self.to_string().serialize(serializer);
+ }
+
+ self.number_from_monday().serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Weekday {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Weekday
+
+// region: Month
+impl Serialize for Month {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ #[cfg(not(feature = "std"))]
+ use alloc::string::String;
+ return self.to_string().serialize(serializer);
+ }
+
+ (*self as u8).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Month {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Month
diff --git a/third_party/rust/time/src/serde/rfc2822.rs b/third_party/rust/time/src/serde/rfc2822.rs
new file mode 100644
index 0000000000..eca90f5204
--- /dev/null
+++ b/third_party/rust/time/src/serde/rfc2822.rs
@@ -0,0 +1,72 @@
+//! Use the well-known [RFC2822 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::Rfc2822;
+use crate::OffsetDateTime;
+
+/// Serialize an [`OffsetDateTime`] using the well-known RFC2822 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Rfc2822)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+}
+
+/// Deserialize an [`OffsetDateTime`] from its RFC2822 representation.
+#[cfg(feature = "parsing")]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ deserializer.deserialize_str(Visitor::<Rfc2822>(PhantomData))
+}
+
+/// Use the well-known [RFC2822 format] when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC2822 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Rfc2822))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its RFC2822 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Rfc2822>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/rfc3339.rs b/third_party/rust/time/src/serde/rfc3339.rs
new file mode 100644
index 0000000000..b1ffb25130
--- /dev/null
+++ b/third_party/rust/time/src/serde/rfc3339.rs
@@ -0,0 +1,72 @@
+//! Use the well-known [RFC3339 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::Rfc3339;
+use crate::OffsetDateTime;
+
+/// Serialize an [`OffsetDateTime`] using the well-known RFC3339 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Rfc3339)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+}
+
+/// Deserialize an [`OffsetDateTime`] from its RFC3339 representation.
+#[cfg(feature = "parsing")]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ deserializer.deserialize_str(Visitor::<Rfc3339>(PhantomData))
+}
+
+/// Use the well-known [RFC3339 format] when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC3339 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Rfc3339))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its RFC3339 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Rfc3339>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/timestamp.rs b/third_party/rust/time/src/serde/timestamp.rs
new file mode 100644
index 0000000000..d86e6b9336
--- /dev/null
+++ b/third_party/rust/time/src/serde/timestamp.rs
@@ -0,0 +1,60 @@
+//! Treat an [`OffsetDateTime`] as a [Unix timestamp] for the purposes of serde.
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! When deserializing, the offset is assumed to be UTC.
+//!
+//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
+//! [with]: https://serde.rs/field-attrs.html#with
+
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
+
+use crate::OffsetDateTime;
+
+/// Serialize an `OffsetDateTime` as its Unix timestamp
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime.unix_timestamp().serialize(serializer)
+}
+
+/// Deserialize an `OffsetDateTime` from its Unix timestamp
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ OffsetDateTime::from_unix_timestamp(<_>::deserialize(deserializer)?)
+ .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
+}
+
+/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] for the purposes of
+/// serde.
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// When deserializing, the offset is assumed to be UTC.
+///
+/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an `Option<OffsetDateTime>` as its Unix timestamp
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(OffsetDateTime::unix_timestamp)
+ .serialize(serializer)
+ }
+
+ /// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ Option::deserialize(deserializer)?
+ .map(OffsetDateTime::from_unix_timestamp)
+ .transpose()
+ .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
+ }
+}
diff --git a/third_party/rust/time/src/serde/visitor.rs b/third_party/rust/time/src/serde/visitor.rs
new file mode 100644
index 0000000000..e61989afde
--- /dev/null
+++ b/third_party/rust/time/src/serde/visitor.rs
@@ -0,0 +1,316 @@
+//! Serde visitor for various types.
+
+use core::fmt;
+use core::marker::PhantomData;
+
+use serde::de;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+
+#[cfg(feature = "parsing")]
+use super::{
+ DATE_FORMAT, OFFSET_DATE_TIME_FORMAT, PRIMITIVE_DATE_TIME_FORMAT, TIME_FORMAT,
+ UTC_OFFSET_FORMAT,
+};
+use crate::error::ComponentRange;
+#[cfg(feature = "parsing")]
+use crate::format_description::well_known::*;
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// A serde visitor for various types.
+pub(super) struct Visitor<T: ?Sized>(pub(super) PhantomData<T>);
+
+impl<'a> de::Visitor<'a> for Visitor<Date> {
+ type Value = Date;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Date`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Date, E> {
+ Date::parse(value, &DATE_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Date, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ Date::from_ordinal_date(year, ordinal).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Duration> {
+ type Value = Duration;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Duration`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Duration, E> {
+ let (seconds, nanoseconds) = value.split_once('.').ok_or_else(|| {
+ de::Error::invalid_value(de::Unexpected::Str(value), &"a decimal point")
+ })?;
+
+ let seconds = seconds
+ .parse()
+ .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(seconds), &"seconds"))?;
+ let mut nanoseconds = nanoseconds.parse().map_err(|_| {
+ de::Error::invalid_value(de::Unexpected::Str(nanoseconds), &"nanoseconds")
+ })?;
+
+ if seconds < 0 {
+ nanoseconds *= -1;
+ }
+
+ Ok(Duration::new(seconds, nanoseconds))
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Duration, A::Error> {
+ let seconds = item!(seq, "seconds")?;
+ let nanoseconds = item!(seq, "nanoseconds")?;
+ Ok(Duration::new(seconds, nanoseconds))
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<OffsetDateTime> {
+ type Value = OffsetDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("an `OffsetDateTime`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
+ OffsetDateTime::parse(value, &OFFSET_DATE_TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<OffsetDateTime, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+ let offset_hours = item!(seq, "offset hours")?;
+ let offset_minutes = item!(seq, "offset minutes")?;
+ let offset_seconds = item!(seq, "offset seconds")?;
+
+ Date::from_ordinal_date(year, ordinal)
+ .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
+ .and_then(|datetime| {
+ UtcOffset::from_hms(offset_hours, offset_minutes, offset_seconds)
+ .map(|offset| datetime.assume_offset(offset))
+ })
+ .map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<PrimitiveDateTime> {
+ type Value = PrimitiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `PrimitiveDateTime`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<PrimitiveDateTime, E> {
+ PrimitiveDateTime::parse(value, &PRIMITIVE_DATE_TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<PrimitiveDateTime, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+
+ Date::from_ordinal_date(year, ordinal)
+ .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
+ .map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Time> {
+ type Value = Time;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Time`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Time, E> {
+ Time::parse(value, &TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Time, A::Error> {
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+
+ Time::from_hms_nano(hour, minute, second, nanosecond).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<UtcOffset> {
+ type Value = UtcOffset;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `UtcOffset`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<UtcOffset, E> {
+ UtcOffset::parse(value, &UTC_OFFSET_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<UtcOffset, A::Error> {
+ let hours = item!(seq, "offset hours")?;
+ let minutes = item!(seq, "offset minutes")?;
+ let seconds = item!(seq, "offset seconds")?;
+
+ UtcOffset::from_hms(hours, minutes, seconds).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Weekday> {
+ type Value = Weekday;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Weekday`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Weekday, E> {
+ match value {
+ "Monday" => Ok(Weekday::Monday),
+ "Tuesday" => Ok(Weekday::Tuesday),
+ "Wednesday" => Ok(Weekday::Wednesday),
+ "Thursday" => Ok(Weekday::Thursday),
+ "Friday" => Ok(Weekday::Friday),
+ "Saturday" => Ok(Weekday::Saturday),
+ "Sunday" => Ok(Weekday::Sunday),
+ _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Weekday`")),
+ }
+ }
+
+ fn visit_u64<E: de::Error>(self, value: u64) -> Result<Weekday, E> {
+ match value {
+ 1 => Ok(Weekday::Monday),
+ 2 => Ok(Weekday::Tuesday),
+ 3 => Ok(Weekday::Wednesday),
+ 4 => Ok(Weekday::Thursday),
+ 5 => Ok(Weekday::Friday),
+ 6 => Ok(Weekday::Saturday),
+ 7 => Ok(Weekday::Sunday),
+ _ => Err(E::invalid_value(
+ de::Unexpected::Unsigned(value),
+ &"a value in the range 1..=7",
+ )),
+ }
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Month> {
+ type Value = Month;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Month`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Month, E> {
+ match value {
+ "January" => Ok(Month::January),
+ "February" => Ok(Month::February),
+ "March" => Ok(Month::March),
+ "April" => Ok(Month::April),
+ "May" => Ok(Month::May),
+ "June" => Ok(Month::June),
+ "July" => Ok(Month::July),
+ "August" => Ok(Month::August),
+ "September" => Ok(Month::September),
+ "October" => Ok(Month::October),
+ "November" => Ok(Month::November),
+ "December" => Ok(Month::December),
+ _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Month`")),
+ }
+ }
+
+ fn visit_u64<E: de::Error>(self, value: u64) -> Result<Month, E> {
+ match value {
+ 1 => Ok(Month::January),
+ 2 => Ok(Month::February),
+ 3 => Ok(Month::March),
+ 4 => Ok(Month::April),
+ 5 => Ok(Month::May),
+ 6 => Ok(Month::June),
+ 7 => Ok(Month::July),
+ 8 => Ok(Month::August),
+ 9 => Ok(Month::September),
+ 10 => Ok(Month::October),
+ 11 => Ok(Month::November),
+ 12 => Ok(Month::December),
+ _ => Err(E::invalid_value(
+ de::Unexpected::Unsigned(value),
+ &"a value in the range 1..=12",
+ )),
+ }
+ }
+}
+
+/// Implement a visitor for a well-known format.
+macro_rules! well_known {
+ ($article:literal, $name:literal, $($ty:tt)+) => {
+ #[cfg(feature = "parsing")]
+ impl<'a> de::Visitor<'a> for Visitor<$($ty)+> {
+ type Value = OffsetDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(concat!($article, " ", $name, "-formatted `OffsetDateTime`"))
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
+ OffsetDateTime::parse(value, &$($ty)+).map_err(E::custom)
+ }
+ }
+
+ #[cfg(feature = "parsing")]
+ impl<'a> de::Visitor<'a> for Visitor<Option<$($ty)+>> {
+ type Value = Option<OffsetDateTime>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(concat!(
+ $article,
+ " ",
+ $name,
+ "-formatted `Option<OffsetDateTime>`"
+ ))
+ }
+
+ fn visit_some<D: Deserializer<'a>>(
+ self,
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer
+ .deserialize_any(Visitor::<$($ty)+>(PhantomData))
+ .map(Some)
+ }
+
+ fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> {
+ Ok(None)
+ }
+
+ fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+ }
+ };
+}
+
+well_known!("an", "RFC2822", Rfc2822);
+well_known!("an", "RFC3339", Rfc3339);
+well_known!(
+ "an",
+ "ISO 8601",
+ Iso8601::<{ super::iso8601::SERDE_CONFIG }>
+);
diff --git a/third_party/rust/time/src/sys/local_offset_at/imp.rs b/third_party/rust/time/src/sys/local_offset_at/imp.rs
new file mode 100644
index 0000000000..251fa667c2
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/imp.rs
@@ -0,0 +1,8 @@
+//! A fallback for any OS not covered.
+
+use crate::{OffsetDateTime, UtcOffset};
+
+#[allow(clippy::missing_docs_in_private_items)]
+pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> {
+ None
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/mod.rs b/third_party/rust/time/src/sys/local_offset_at/mod.rs
new file mode 100644
index 0000000000..f0bc4be3cc
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/mod.rs
@@ -0,0 +1,23 @@
+//! A method to obtain the local offset from UTC.
+
+#![allow(clippy::missing_const_for_fn)]
+
+#[cfg_attr(target_family = "windows", path = "windows.rs")]
+#[cfg_attr(target_family = "unix", path = "unix.rs")]
+#[cfg_attr(
+ all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ ),
+ path = "wasm_js.rs"
+)]
+mod imp;
+
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is
+/// returned.
+pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ imp::local_offset_at(datetime)
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/unix.rs b/third_party/rust/time/src/sys/local_offset_at/unix.rs
new file mode 100644
index 0000000000..6e849892da
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/unix.rs
@@ -0,0 +1,169 @@
+//! Get the system's UTC offset on Unix.
+
+use core::mem::MaybeUninit;
+
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error.
+///
+/// # Safety
+///
+/// This method must only be called when the process is single-threaded.
+///
+/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior
+/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set
+/// environment variables that makes it unsafe.
+unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
+ extern "C" {
+ #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")]
+ fn tzset();
+ }
+
+ // The exact type of `timestamp` beforehand can vary, so this conversion is necessary.
+ #[allow(clippy::useless_conversion)]
+ let timestamp = timestamp.try_into().ok()?;
+
+ let mut tm = MaybeUninit::uninit();
+
+ // Update timezone information from system. `localtime_r` does not do this for us.
+ //
+ // Safety: tzset is thread-safe.
+ unsafe { tzset() };
+
+ // Safety: We are calling a system API, which mutates the `tm` variable. If a null
+ // pointer is returned, an error occurred.
+ let tm_ptr = unsafe { libc::localtime_r(&timestamp, tm.as_mut_ptr()) };
+
+ if tm_ptr.is_null() {
+ None
+ } else {
+ // Safety: The value was initialized, as we no longer have a null pointer.
+ Some(unsafe { tm.assume_init() })
+ }
+}
+
+/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
+// This is available to any target known to have the `tm_gmtoff` extension.
+#[cfg(any(
+ target_os = "redox",
+ target_os = "linux",
+ target_os = "l4re",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "watchos",
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "haiku",
+))]
+fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
+ let seconds: i32 = tm.tm_gmtoff.try_into().ok()?;
+ UtcOffset::from_hms(
+ (seconds / 3_600) as _,
+ ((seconds / 60) % 60) as _,
+ (seconds % 60) as _,
+ )
+ .ok()
+}
+
+/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
+#[cfg(all(
+ not(unsound_local_offset),
+ not(any(
+ target_os = "redox",
+ target_os = "linux",
+ target_os = "l4re",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "watchos",
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "haiku",
+ ))
+))]
+#[allow(unused_variables, clippy::missing_const_for_fn)]
+fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
+ None
+}
+
+/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
+// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such
+// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is
+// that daylight saving time does not start on the same date every year, nor are the rules for
+// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to
+// every other year, which is not always the case.
+#[cfg(all(
+ unsound_local_offset,
+ not(any(
+ target_os = "redox",
+ target_os = "linux",
+ target_os = "l4re",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "watchos",
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "haiku",
+ ))
+))]
+fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
+ use crate::Date;
+
+ let mut tm = tm;
+ if tm.tm_sec == 60 {
+ // Leap seconds are not currently supported.
+ tm.tm_sec = 59;
+ }
+
+ let local_timestamp =
+ Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1)
+ .ok()?
+ .with_hms(
+ tm.tm_hour.try_into().ok()?,
+ tm.tm_min.try_into().ok()?,
+ tm.tm_sec.try_into().ok()?,
+ )
+ .ok()?
+ .assume_utc()
+ .unix_timestamp();
+
+ let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp())
+ .try_into()
+ .ok()?;
+
+ UtcOffset::from_hms(
+ (diff_secs / 3_600) as _,
+ ((diff_secs / 60) % 60) as _,
+ (diff_secs % 60) as _,
+ )
+ .ok()
+}
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ // Ensure that the process is single-threaded unless the user has explicitly opted out of this
+ // check. This is to prevent issues with the environment being mutated by a different thread in
+ // the process while execution of this function is taking place, which can cause a segmentation
+ // fault by dereferencing a dangling pointer.
+ // If the `num_threads` crate is incapable of determining the number of running threads, then
+ // we conservatively return `None` to avoid a soundness bug.
+ if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) {
+ return None;
+ }
+
+ // Safety: We have just confirmed that the process is single-threaded or the user has explicitly
+ // opted out of soundness.
+ let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?;
+ tm_to_offset(tm)
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs
new file mode 100644
index 0000000000..fcea4b0f5a
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs
@@ -0,0 +1,12 @@
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ let js_date: js_sys::Date = datetime.into();
+ // The number of minutes returned by getTimezoneOffset() is positive if the local time zone
+ // is behind UTC, and negative if the local time zone is ahead of UTC. For example,
+ // for UTC+10, -600 will be returned.
+ let timezone_offset = (js_date.get_timezone_offset() as i32) * -60;
+
+ UtcOffset::from_whole_seconds(timezone_offset).ok()
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/windows.rs b/third_party/rust/time/src/sys/local_offset_at/windows.rs
new file mode 100644
index 0000000000..fed01bf3a9
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/windows.rs
@@ -0,0 +1,114 @@
+//! Get the system's UTC offset on Windows.
+
+use core::mem::MaybeUninit;
+
+use crate::{OffsetDateTime, UtcOffset};
+
+// ffi: WINAPI FILETIME struct
+#[repr(C)]
+#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
+struct FileTime {
+ dwLowDateTime: u32,
+ dwHighDateTime: u32,
+}
+
+// ffi: WINAPI SYSTEMTIME struct
+#[repr(C)]
+#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
+struct SystemTime {
+ wYear: u16,
+ wMonth: u16,
+ wDayOfWeek: u16,
+ wDay: u16,
+ wHour: u16,
+ wMinute: u16,
+ wSecond: u16,
+ wMilliseconds: u16,
+}
+
+#[link(name = "kernel32")]
+extern "system" {
+ // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime
+ fn SystemTimeToFileTime(lpSystemTime: *const SystemTime, lpFileTime: *mut FileTime) -> i32;
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime
+ fn SystemTimeToTzSpecificLocalTime(
+ lpTimeZoneInformation: *const core::ffi::c_void, // We only pass `nullptr` here
+ lpUniversalTime: *const SystemTime,
+ lpLocalTime: *mut SystemTime,
+ ) -> i32;
+}
+
+/// Convert a `SYSTEMTIME` to a `FILETIME`. Returns `None` if any error occurred.
+fn systemtime_to_filetime(systime: &SystemTime) -> Option<FileTime> {
+ let mut ft = MaybeUninit::uninit();
+
+ // Safety: `SystemTimeToFileTime` is thread-safe.
+ if 0 == unsafe { SystemTimeToFileTime(systime, ft.as_mut_ptr()) } {
+ // failed
+ None
+ } else {
+ // Safety: The call succeeded.
+ Some(unsafe { ft.assume_init() })
+ }
+}
+
+/// Convert a `FILETIME` to an `i64`, representing a number of seconds.
+fn filetime_to_secs(filetime: &FileTime) -> i64 {
+ /// FILETIME represents 100-nanosecond intervals
+ const FT_TO_SECS: i64 = 10_000_000;
+ ((filetime.dwHighDateTime as i64) << 32 | filetime.dwLowDateTime as i64) / FT_TO_SECS
+}
+
+/// Convert an [`OffsetDateTime`] to a `SYSTEMTIME`.
+fn offset_to_systemtime(datetime: OffsetDateTime) -> SystemTime {
+ let (_, month, day_of_month) = datetime.to_offset(UtcOffset::UTC).date().to_calendar_date();
+ SystemTime {
+ wYear: datetime.year() as _,
+ wMonth: month as _,
+ wDay: day_of_month as _,
+ wDayOfWeek: 0, // ignored
+ wHour: datetime.hour() as _,
+ wMinute: datetime.minute() as _,
+ wSecond: datetime.second() as _,
+ wMilliseconds: datetime.millisecond(),
+ }
+}
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ // This function falls back to UTC if any system call fails.
+ let systime_utc = offset_to_systemtime(datetime.to_offset(UtcOffset::UTC));
+
+ // Safety: `local_time` is only read if it is properly initialized, and
+ // `SystemTimeToTzSpecificLocalTime` is thread-safe.
+ let systime_local = unsafe {
+ let mut local_time = MaybeUninit::uninit();
+
+ if 0 == SystemTimeToTzSpecificLocalTime(
+ core::ptr::null(), // use system's current timezone
+ &systime_utc,
+ local_time.as_mut_ptr(),
+ ) {
+ // call failed
+ return None;
+ } else {
+ local_time.assume_init()
+ }
+ };
+
+ // Convert SYSTEMTIMEs to FILETIMEs so we can perform arithmetic on them.
+ let ft_system = systemtime_to_filetime(&systime_utc)?;
+ let ft_local = systemtime_to_filetime(&systime_local)?;
+
+ let diff_secs: i32 = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system))
+ .try_into()
+ .ok()?;
+
+ UtcOffset::from_hms(
+ (diff_secs / 3_600) as _,
+ ((diff_secs / 60) % 60) as _,
+ (diff_secs % 60) as _,
+ )
+ .ok()
+}
diff --git a/third_party/rust/time/src/sys/mod.rs b/third_party/rust/time/src/sys/mod.rs
new file mode 100644
index 0000000000..90bbf7ff3b
--- /dev/null
+++ b/third_party/rust/time/src/sys/mod.rs
@@ -0,0 +1,9 @@
+//! Functions with a common interface that rely on system calls.
+
+#![allow(unsafe_code)] // We're interfacing with system calls.
+
+#[cfg(feature = "local-offset")]
+mod local_offset_at;
+
+#[cfg(feature = "local-offset")]
+pub(crate) use local_offset_at::local_offset_at;
diff --git a/third_party/rust/time/src/tests.rs b/third_party/rust/time/src/tests.rs
new file mode 100644
index 0000000000..66c977d91d
--- /dev/null
+++ b/third_party/rust/time/src/tests.rs
@@ -0,0 +1,113 @@
+#![cfg(all( // require `--all-features` to be passed
+ 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",
+))]
+#![allow(
+ clippy::let_underscore_drop,
+ clippy::clone_on_copy,
+ clippy::cognitive_complexity,
+ clippy::std_instead_of_core
+)]
+
+//! Tests for internal details.
+//!
+//! This module should only be used when it is not possible to test the implementation in a
+//! reasonable manner externally.
+
+use std::num::NonZeroU8;
+
+use crate::formatting::DigitCount;
+use crate::parsing::combinator::rfc::iso8601;
+use crate::parsing::shim::Integer;
+use crate::{duration, parsing};
+
+#[test]
+fn digit_count() {
+ assert_eq!(1_u8.num_digits(), 1);
+ assert_eq!(9_u8.num_digits(), 1);
+ assert_eq!(10_u8.num_digits(), 2);
+ assert_eq!(99_u8.num_digits(), 2);
+ assert_eq!(100_u8.num_digits(), 3);
+
+ assert_eq!(1_u16.num_digits(), 1);
+ assert_eq!(9_u16.num_digits(), 1);
+ assert_eq!(10_u16.num_digits(), 2);
+ assert_eq!(99_u16.num_digits(), 2);
+ assert_eq!(100_u16.num_digits(), 3);
+ assert_eq!(999_u16.num_digits(), 3);
+ assert_eq!(1_000_u16.num_digits(), 4);
+ assert_eq!(9_999_u16.num_digits(), 4);
+ assert_eq!(10_000_u16.num_digits(), 5);
+
+ assert_eq!(1_u32.num_digits(), 1);
+ assert_eq!(9_u32.num_digits(), 1);
+ assert_eq!(10_u32.num_digits(), 2);
+ assert_eq!(99_u32.num_digits(), 2);
+ assert_eq!(100_u32.num_digits(), 3);
+ assert_eq!(999_u32.num_digits(), 3);
+ assert_eq!(1_000_u32.num_digits(), 4);
+ assert_eq!(9_999_u32.num_digits(), 4);
+ assert_eq!(10_000_u32.num_digits(), 5);
+ assert_eq!(99_999_u32.num_digits(), 5);
+ assert_eq!(100_000_u32.num_digits(), 6);
+ assert_eq!(999_999_u32.num_digits(), 6);
+ assert_eq!(1_000_000_u32.num_digits(), 7);
+ assert_eq!(9_999_999_u32.num_digits(), 7);
+ assert_eq!(10_000_000_u32.num_digits(), 8);
+ assert_eq!(99_999_999_u32.num_digits(), 8);
+ assert_eq!(100_000_000_u32.num_digits(), 9);
+ assert_eq!(999_999_999_u32.num_digits(), 9);
+ assert_eq!(1_000_000_000_u32.num_digits(), 10);
+}
+
+#[test]
+fn default() {
+ assert_eq!(
+ duration::Padding::Optimize.clone(),
+ duration::Padding::default()
+ );
+}
+
+#[test]
+fn debug() {
+ let _ = format!("{:?}", duration::Padding::Optimize);
+ let _ = format!("{:?}", parsing::ParsedItem(b"", 0));
+ let _ = format!("{:?}", parsing::component::Period::Am);
+ let _ = format!("{:?}", iso8601::ExtendedKind::Basic);
+}
+
+#[test]
+fn clone() {
+ assert_eq!(
+ parsing::component::Period::Am.clone(),
+ parsing::component::Period::Am
+ );
+ // does not impl Debug
+ assert!(crate::time::Padding::Optimize.clone() == crate::time::Padding::Optimize);
+ // does not impl PartialEq
+ assert!(matches!(
+ iso8601::ExtendedKind::Basic.clone(),
+ iso8601::ExtendedKind::Basic
+ ));
+}
+
+#[test]
+fn parsing_internals() {
+ assert!(
+ parsing::ParsedItem(b"", ())
+ .flat_map(|_| None::<()>)
+ .is_none()
+ );
+ assert!(<NonZeroU8 as Integer>::parse_bytes(b"256").is_none());
+}
diff --git a/third_party/rust/time/src/time.rs b/third_party/rust/time/src/time.rs
new file mode 100644
index 0000000000..32fa97790f
--- /dev/null
+++ b/third_party/rust/time/src/time.rs
@@ -0,0 +1,761 @@
+//! The [`Time`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::util::DateAdjustment;
+use crate::{error, Duration};
+
+/// By explicitly inserting this enum where padding is expected, the compiler is able to better
+/// perform niche value optimization.
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub(crate) enum Padding {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Optimize,
+}
+
+/// The clock time within a given date. Nanosecond precision.
+///
+/// All minutes are assumed to have exactly 60 seconds; no attempt is made to handle leap seconds
+/// (either positive or negative).
+///
+/// When comparing two `Time`s, they are assumed to be in the same calendar date.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Time {
+ #[allow(clippy::missing_docs_in_private_items)]
+ hour: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ minute: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ second: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ nanosecond: u32,
+ #[allow(clippy::missing_docs_in_private_items)]
+ padding: Padding,
+}
+
+impl Time {
+ /// Create a `Time` that is exactly midnight.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// # use time_macros::time;
+ /// assert_eq!(Time::MIDNIGHT, time!(0:00));
+ /// ```
+ pub const MIDNIGHT: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0);
+
+ /// The smallest value that can be represented by `Time`.
+ ///
+ /// `00:00:00.0`
+ pub(crate) const MIN: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0);
+
+ /// The largest value that can be represented by `Time`.
+ ///
+ /// `23:59:59.999_999_999`
+ pub(crate) const MAX: Self = Self::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999);
+
+ // region: constructors
+ /// Create a `Time` from its components.
+ #[doc(hidden)]
+ pub const fn __from_hms_nanos_unchecked(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Self {
+ debug_assert!(hour < 24);
+ debug_assert!(minute < 60);
+ debug_assert!(second < 60);
+ debug_assert!(nanosecond < 1_000_000_000);
+
+ Self {
+ hour,
+ minute,
+ second,
+ nanosecond,
+ padding: Padding::Optimize,
+ }
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms(1, 2, 3).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms(24, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms(0, 60, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms(0, 0, 60).is_err()); // 60 isn't a valid second.
+ /// ```
+ pub const fn from_hms(hour: u8, minute: u8, second: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(hour, minute, second, 0))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_milli(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_milli(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_milli(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_milli(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_milli(0, 0, 0, 1_000).is_err()); // 1_000 isn't a valid millisecond.
+ /// ```
+ pub const fn from_hms_milli(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(millisecond in 0 => 999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ millisecond as u32 * 1_000_000,
+ ))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_micro(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_micro(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_micro(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_micro(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_micro(0, 0, 0, 1_000_000).is_err()); // 1_000_000 isn't a valid microsecond.
+ /// ```
+ pub const fn from_hms_micro(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(microsecond in 0 => 999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ microsecond * 1_000,
+ ))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_nano(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_nano(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_nano(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_nano(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_nano(0, 0, 0, 1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond.
+ /// ```
+ pub const fn from_hms_nano(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(nanosecond in 0 => 999_999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour, minute, second, nanosecond,
+ ))
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms(), (0, 0, 0));
+ /// assert_eq!(time!(23:59:59).as_hms(), (23, 59, 59));
+ /// ```
+ pub const fn as_hms(self) -> (u8, u8, u8) {
+ (self.hour, self.minute, self.second)
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_milli(), (0, 0, 0, 0));
+ /// assert_eq!(time!(23:59:59.999).as_hms_milli(), (23, 59, 59, 999));
+ /// ```
+ pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
+ (
+ self.hour,
+ self.minute,
+ self.second,
+ (self.nanosecond / 1_000_000) as u16,
+ )
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_micro(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// time!(23:59:59.999_999).as_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
+ (self.hour, self.minute, self.second, self.nanosecond / 1_000)
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_nano(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// time!(23:59:59.999_999_999).as_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
+ (self.hour, self.minute, self.second, self.nanosecond)
+ }
+
+ /// Get the clock hour.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).hour(), 0);
+ /// assert_eq!(time!(23:59:59).hour(), 23);
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.hour
+ }
+
+ /// Get the minute within the hour.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).minute(), 0);
+ /// assert_eq!(time!(23:59:59).minute(), 59);
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.minute
+ }
+
+ /// Get the second within the minute.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).second(), 0);
+ /// assert_eq!(time!(23:59:59).second(), 59);
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.second
+ }
+
+ /// Get the milliseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).millisecond(), 0);
+ /// assert_eq!(time!(23:59:59.999).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ (self.nanosecond / 1_000_000) as _
+ }
+
+ /// Get the microseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).microsecond(), 0);
+ /// assert_eq!(time!(23:59:59.999_999).microsecond(), 999_999);
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.nanosecond / 1_000
+ }
+
+ /// Get the nanoseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).nanosecond(), 0);
+ /// assert_eq!(time!(23:59:59.999_999_999).nanosecond(), 999_999_999);
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.nanosecond
+ }
+ // endregion getters
+
+ // region: arithmetic helpers
+ /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning whether
+ /// the date is different.
+ pub(crate) const fn adjusting_add(self, duration: Duration) -> (DateAdjustment, Self) {
+ let mut nanoseconds = self.nanosecond as i32 + duration.subsec_nanoseconds();
+ let mut seconds = self.second as i8 + (duration.whole_seconds() % 60) as i8;
+ let mut minutes = self.minute as i8 + (duration.whole_minutes() % 60) as i8;
+ let mut hours = self.hour as i8 + (duration.whole_hours() % 24) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..1_000_000_000 => seconds);
+ cascade!(seconds in 0..60 => minutes);
+ cascade!(minutes in 0..60 => hours);
+ if hours >= 24 {
+ hours -= 24;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += 24;
+ date_adjustment = DateAdjustment::Previous;
+ }
+
+ (
+ date_adjustment,
+ Self::__from_hms_nanos_unchecked(
+ hours as _,
+ minutes as _,
+ seconds as _,
+ nanoseconds as _,
+ ),
+ )
+ }
+
+ /// Subtract the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning
+ /// whether the date is different.
+ pub(crate) const fn adjusting_sub(self, duration: Duration) -> (DateAdjustment, Self) {
+ let mut nanoseconds = self.nanosecond as i32 - duration.subsec_nanoseconds();
+ let mut seconds = self.second as i8 - (duration.whole_seconds() % 60) as i8;
+ let mut minutes = self.minute as i8 - (duration.whole_minutes() % 60) as i8;
+ let mut hours = self.hour as i8 - (duration.whole_hours() % 24) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..1_000_000_000 => seconds);
+ cascade!(seconds in 0..60 => minutes);
+ cascade!(minutes in 0..60 => hours);
+ if hours >= 24 {
+ hours -= 24;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += 24;
+ date_adjustment = DateAdjustment::Previous;
+ }
+
+ (
+ date_adjustment,
+ Self::__from_hms_nanos_unchecked(
+ hours as _,
+ minutes as _,
+ seconds as _,
+ nanoseconds as _,
+ ),
+ )
+ }
+
+ /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow,
+ /// returning whether the date is the previous date as the first element of the tuple.
+ pub(crate) const fn adjusting_add_std(self, duration: StdDuration) -> (bool, Self) {
+ let mut nanosecond = self.nanosecond + duration.subsec_nanos();
+ let mut second = self.second + (duration.as_secs() % 60) as u8;
+ let mut minute = self.minute + ((duration.as_secs() / 60) % 60) as u8;
+ let mut hour = self.hour + ((duration.as_secs() / 3_600) % 24) as u8;
+ let mut is_next_day = false;
+
+ cascade!(nanosecond in 0..1_000_000_000 => second);
+ cascade!(second in 0..60 => minute);
+ cascade!(minute in 0..60 => hour);
+ if hour >= 24 {
+ hour -= 24;
+ is_next_day = true;
+ }
+
+ (
+ is_next_day,
+ Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond),
+ )
+ }
+
+ /// Subtract the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow,
+ /// returning whether the date is the previous date as the first element of the tuple.
+ pub(crate) const fn adjusting_sub_std(self, duration: StdDuration) -> (bool, Self) {
+ let mut nanosecond = self.nanosecond as i32 - duration.subsec_nanos() as i32;
+ let mut second = self.second as i8 - (duration.as_secs() % 60) as i8;
+ let mut minute = self.minute as i8 - ((duration.as_secs() / 60) % 60) as i8;
+ let mut hour = self.hour as i8 - ((duration.as_secs() / 3_600) % 24) as i8;
+ let mut is_previous_day = false;
+
+ cascade!(nanosecond in 0..1_000_000_000 => second);
+ cascade!(second in 0..60 => minute);
+ cascade!(minute in 0..60 => hour);
+ if hour < 0 {
+ hour += 24;
+ is_previous_day = true;
+ }
+
+ (
+ is_previous_day,
+ Self::__from_hms_nanos_unchecked(hour as _, minute as _, second as _, nanosecond as _),
+ )
+ }
+ // endregion arithmetic helpers
+
+ // region: replacement
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_hour(7),
+ /// Ok(time!(07:02:03.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ self.minute,
+ self.second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_minute(7),
+ /// Ok(time!(01:07:03.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(minute in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ minute,
+ self.second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_second(7),
+ /// Ok(time!(01:02:07.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(second in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_millisecond(7),
+ /// Ok(time!(01:02:03.007))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(millisecond in 0 => 999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ millisecond as u32 * 1_000_000,
+ ))
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_microsecond(7_008),
+ /// Ok(time!(01:02:03.007_008))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(microsecond in 0 => 999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ microsecond * 1000,
+ ))
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_nanosecond(7_008_009),
+ /// Ok(time!(01:02:03.007_008_009))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(nanosecond in 0 => 999_999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ nanosecond,
+ ))
+ }
+ // endregion replacement
+}
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl Time {
+ /// Format the `Time` using the provided [format description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, crate::error::Format> {
+ format.format_into(output, None, Some(self), None)
+ }
+
+ /// Format the `Time` using the provided [format description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::time;
+ /// let format = format_description::parse("[hour]:[minute]:[second]")?;
+ /// assert_eq!(time!(12:00).format(&format)?, "12:00:00");
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(
+ self,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<String, crate::error::Format> {
+ format.format(None, Some(self), None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl Time {
+ /// Parse a `Time` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// # use time_macros::{time, format_description};
+ /// let format = format_description!("[hour]:[minute]:[second]");
+ /// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_time(input.as_bytes())
+ }
+}
+
+impl fmt::Display for Time {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let (value, width) = match self.nanosecond() {
+ nanos if nanos % 10 != 0 => (nanos, 9),
+ nanos if (nanos / 10) % 10 != 0 => (nanos / 10, 8),
+ nanos if (nanos / 100) % 10 != 0 => (nanos / 100, 7),
+ nanos if (nanos / 1_000) % 10 != 0 => (nanos / 1_000, 6),
+ nanos if (nanos / 10_000) % 10 != 0 => (nanos / 10_000, 5),
+ nanos if (nanos / 100_000) % 10 != 0 => (nanos / 100_000, 4),
+ nanos if (nanos / 1_000_000) % 10 != 0 => (nanos / 1_000_000, 3),
+ nanos if (nanos / 10_000_000) % 10 != 0 => (nanos / 10_000_000, 2),
+ nanos => (nanos / 100_000_000, 1),
+ };
+ write!(
+ f,
+ "{}:{:02}:{:02}.{value:0width$}",
+ self.hour, self.minute, self.second,
+ )
+ }
+}
+
+impl fmt::Debug for Time {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl Add<Duration> for Time {
+ type Output = Self;
+
+ /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(12:00) + 2.hours(), time!(14:00));
+ /// assert_eq!(time!(0:00:01) + (-2).seconds(), time!(23:59:59));
+ /// ```
+ fn add(self, duration: Duration) -> Self::Output {
+ self.adjusting_add(duration).1
+ }
+}
+
+impl Add<StdDuration> for Time {
+ type Output = Self;
+
+ /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalStdDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(12:00) + 2.std_hours(), time!(14:00));
+ /// assert_eq!(time!(23:59:59) + 2.std_seconds(), time!(0:00:01));
+ /// ```
+ fn add(self, duration: StdDuration) -> Self::Output {
+ self.adjusting_add_std(duration).1
+ }
+}
+
+impl_add_assign!(Time: Duration, StdDuration);
+
+impl Sub<Duration> for Time {
+ type Output = Self;
+
+ /// Subtract the sub-day time of the [`Duration`] from the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(14:00) - 2.hours(), time!(12:00));
+ /// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01));
+ /// ```
+ fn sub(self, duration: Duration) -> Self::Output {
+ self.adjusting_sub(duration).1
+ }
+}
+
+impl Sub<StdDuration> for Time {
+ type Output = Self;
+
+ /// Subtract the sub-day time of the [`std::time::Duration`] from the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalStdDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(14:00) - 2.std_hours(), time!(12:00));
+ /// assert_eq!(time!(0:00:01) - 2.std_seconds(), time!(23:59:59));
+ /// ```
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ self.adjusting_sub_std(duration).1
+ }
+}
+
+impl_sub_assign!(Time: Duration, StdDuration);
+
+impl Sub for Time {
+ type Output = Duration;
+
+ /// Subtract two `Time`s, returning the [`Duration`] between. This assumes both `Time`s are in
+ /// the same calendar day.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00) - time!(0:00), 0.seconds());
+ /// assert_eq!(time!(1:00) - time!(0:00), 1.hours());
+ /// assert_eq!(time!(0:00) - time!(1:00), (-1).hours());
+ /// assert_eq!(time!(0:00) - time!(23:00), (-23).hours());
+ /// ```
+ fn sub(self, rhs: Self) -> Self::Output {
+ let hour_diff = (self.hour as i8) - (rhs.hour as i8);
+ let minute_diff = (self.minute as i8) - (rhs.minute as i8);
+ let second_diff = (self.second as i8) - (rhs.second as i8);
+ let nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32);
+
+ let seconds = hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64;
+
+ let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 {
+ (seconds - 1, nanosecond_diff + 1_000_000_000)
+ } else if seconds < 0 && nanosecond_diff > 0 {
+ (seconds + 1, nanosecond_diff - 1_000_000_000)
+ } else {
+ (seconds, nanosecond_diff)
+ };
+
+ Duration::new_unchecked(seconds, nanoseconds)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/utc_offset.rs b/third_party/rust/time/src/utc_offset.rs
new file mode 100644
index 0000000000..d69f0c1e39
--- /dev/null
+++ b/third_party/rust/time/src/utc_offset.rs
@@ -0,0 +1,346 @@
+//! The [`UtcOffset`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::Neg;
+#[cfg(feature = "formatting")]
+use std::io;
+
+use crate::error;
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+#[cfg(feature = "local-offset")]
+use crate::sys::local_offset_at;
+#[cfg(feature = "local-offset")]
+use crate::OffsetDateTime;
+
+/// An offset from UTC.
+///
+/// This struct can store values up to ±23:59:59. If you need support outside this range, please
+/// file an issue with your use case.
+// All three components _must_ have the same sign.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct UtcOffset {
+ #[allow(clippy::missing_docs_in_private_items)]
+ hours: i8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ minutes: i8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ seconds: i8,
+}
+
+impl UtcOffset {
+ /// A `UtcOffset` that is UTC.
+ ///
+ /// ```rust
+ /// # use time::UtcOffset;
+ /// # use time_macros::offset;
+ /// assert_eq!(UtcOffset::UTC, offset!(UTC));
+ /// ```
+ pub const UTC: Self = Self::__from_hms_unchecked(0, 0, 0);
+
+ // region: constructors
+ /// Create a `UtcOffset` representing an offset of the hours, minutes, and seconds provided, the
+ /// validity of which must be guaranteed by the caller. All three parameters must have the same
+ /// sign.
+ #[doc(hidden)]
+ pub const fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self {
+ if hours < 0 {
+ debug_assert!(minutes <= 0);
+ debug_assert!(seconds <= 0);
+ } else if hours > 0 {
+ debug_assert!(minutes >= 0);
+ debug_assert!(seconds >= 0);
+ }
+ if minutes < 0 {
+ debug_assert!(seconds <= 0);
+ } else if minutes > 0 {
+ debug_assert!(seconds >= 0);
+ }
+ debug_assert!(hours.unsigned_abs() < 24);
+ debug_assert!(minutes.unsigned_abs() < 60);
+ debug_assert!(seconds.unsigned_abs() < 60);
+
+ Self {
+ hours,
+ minutes,
+ seconds,
+ }
+ }
+
+ /// Create a `UtcOffset` representing an offset by the number of hours, minutes, and seconds
+ /// provided.
+ ///
+ /// The sign of all three components should match. If they do not, all smaller components will
+ /// have their signs flipped.
+ ///
+ /// ```rust
+ /// # use time::UtcOffset;
+ /// assert_eq!(UtcOffset::from_hms(1, 2, 3)?.as_hms(), (1, 2, 3));
+ /// assert_eq!(UtcOffset::from_hms(1, -2, -3)?.as_hms(), (1, 2, 3));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub const fn from_hms(
+ hours: i8,
+ mut minutes: i8,
+ mut seconds: i8,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hours in -23 => 23);
+ ensure_value_in_range!(minutes in -59 => 59);
+ ensure_value_in_range!(seconds in -59 => 59);
+
+ if (hours > 0 && minutes < 0) || (hours < 0 && minutes > 0) {
+ minutes *= -1;
+ }
+ if (hours > 0 && seconds < 0)
+ || (hours < 0 && seconds > 0)
+ || (minutes > 0 && seconds < 0)
+ || (minutes < 0 && seconds > 0)
+ {
+ seconds *= -1;
+ }
+
+ Ok(Self::__from_hms_unchecked(hours, minutes, seconds))
+ }
+
+ /// Create a `UtcOffset` representing an offset by the number of seconds provided.
+ ///
+ /// ```rust
+ /// # use time::UtcOffset;
+ /// assert_eq!(UtcOffset::from_whole_seconds(3_723)?.as_hms(), (1, 2, 3));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub const fn from_whole_seconds(seconds: i32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(seconds in -86_399 => 86_399);
+
+ Ok(Self::__from_hms_unchecked(
+ (seconds / 3_600) as _,
+ ((seconds / 60) % 60) as _,
+ (seconds % 60) as _,
+ ))
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Obtain the UTC offset as its hours, minutes, and seconds. The sign of all three components
+ /// will always match. A positive value indicates an offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).as_hms(), (1, 2, 3));
+ /// assert_eq!(offset!(-1:02:03).as_hms(), (-1, -2, -3));
+ /// ```
+ pub const fn as_hms(self) -> (i8, i8, i8) {
+ (self.hours, self.minutes, self.seconds)
+ }
+
+ /// Obtain the number of whole hours the offset is from UTC. A positive value indicates an
+ /// offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).whole_hours(), 1);
+ /// assert_eq!(offset!(-1:02:03).whole_hours(), -1);
+ /// ```
+ pub const fn whole_hours(self) -> i8 {
+ self.hours
+ }
+
+ /// Obtain the number of whole minutes the offset is from UTC. A positive value indicates an
+ /// offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).whole_minutes(), 62);
+ /// assert_eq!(offset!(-1:02:03).whole_minutes(), -62);
+ /// ```
+ pub const fn whole_minutes(self) -> i16 {
+ self.hours as i16 * 60 + self.minutes as i16
+ }
+
+ /// Obtain the number of minutes past the hour the offset is from UTC. A positive value
+ /// indicates an offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).minutes_past_hour(), 2);
+ /// assert_eq!(offset!(-1:02:03).minutes_past_hour(), -2);
+ /// ```
+ pub const fn minutes_past_hour(self) -> i8 {
+ self.minutes
+ }
+
+ /// Obtain the number of whole seconds the offset is from UTC. A positive value indicates an
+ /// offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).whole_seconds(), 3723);
+ /// assert_eq!(offset!(-1:02:03).whole_seconds(), -3723);
+ /// ```
+ // This may be useful for anyone manually implementing arithmetic, as it
+ // would let them construct a `Duration` directly.
+ pub const fn whole_seconds(self) -> i32 {
+ self.hours as i32 * 3_600 + self.minutes as i32 * 60 + self.seconds as i32
+ }
+
+ /// Obtain the number of seconds past the minute the offset is from UTC. A positive value
+ /// indicates an offset to the east; a negative to the west.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert_eq!(offset!(+1:02:03).seconds_past_minute(), 3);
+ /// assert_eq!(offset!(-1:02:03).seconds_past_minute(), -3);
+ /// ```
+ pub const fn seconds_past_minute(self) -> i8 {
+ self.seconds
+ }
+ // endregion getters
+
+ // region: is_{sign}
+ /// Check if the offset is exactly UTC.
+ ///
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert!(!offset!(+1:02:03).is_utc());
+ /// assert!(!offset!(-1:02:03).is_utc());
+ /// assert!(offset!(UTC).is_utc());
+ /// ```
+ pub const fn is_utc(self) -> bool {
+ self.hours == 0 && self.minutes == 0 && self.seconds == 0
+ }
+
+ /// Check if the offset is positive, or east of UTC.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert!(offset!(+1:02:03).is_positive());
+ /// assert!(!offset!(-1:02:03).is_positive());
+ /// assert!(!offset!(UTC).is_positive());
+ /// ```
+ pub const fn is_positive(self) -> bool {
+ self.hours > 0 || self.minutes > 0 || self.seconds > 0
+ }
+
+ /// Check if the offset is negative, or west of UTC.
+ ///
+ /// ```rust
+ /// # use time_macros::offset;
+ /// assert!(!offset!(+1:02:03).is_negative());
+ /// assert!(offset!(-1:02:03).is_negative());
+ /// assert!(!offset!(UTC).is_negative());
+ /// ```
+ pub const fn is_negative(self) -> bool {
+ self.hours < 0 || self.minutes < 0 || self.seconds < 0
+ }
+ // endregion is_{sign}
+
+ // region: local offset
+ /// Attempt to obtain the system's UTC offset at a known moment in time. If the offset cannot be
+ /// determined, an error is returned.
+ ///
+ /// ```rust
+ /// # use time::{UtcOffset, OffsetDateTime};
+ /// let local_offset = UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH);
+ /// # if false {
+ /// assert!(local_offset.is_ok());
+ /// # }
+ /// ```
+ #[cfg(feature = "local-offset")]
+ pub fn local_offset_at(datetime: OffsetDateTime) -> Result<Self, error::IndeterminateOffset> {
+ local_offset_at(datetime).ok_or(error::IndeterminateOffset)
+ }
+
+ /// Attempt to obtain the system's current UTC offset. If the offset cannot be determined, an
+ /// error is returned.
+ ///
+ /// ```rust
+ /// # use time::UtcOffset;
+ /// let local_offset = UtcOffset::current_local_offset();
+ /// # if false {
+ /// assert!(local_offset.is_ok());
+ /// # }
+ /// ```
+ #[cfg(feature = "local-offset")]
+ pub fn current_local_offset() -> Result<Self, error::IndeterminateOffset> {
+ let now = OffsetDateTime::now_utc();
+ local_offset_at(now).ok_or(error::IndeterminateOffset)
+ }
+ // endregion: local offset
+}
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl UtcOffset {
+ /// Format the `UtcOffset` using the provided [format description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, error::Format> {
+ format.format_into(output, None, None, Some(self))
+ }
+
+ /// Format the `UtcOffset` using the provided [format description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::offset;
+ /// let format = format_description::parse("[offset_hour sign:mandatory]:[offset_minute]")?;
+ /// assert_eq!(offset!(+1).format(&format)?, "+01:00");
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(None, None, Some(self))
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl UtcOffset {
+ /// Parse a `UtcOffset` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::UtcOffset;
+ /// # use time_macros::{offset, format_description};
+ /// let format = format_description!("[offset_hour]:[offset_minute]");
+ /// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_offset(input.as_bytes())
+ }
+}
+
+impl fmt::Display for UtcOffset {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}{:02}:{:02}:{:02}",
+ if self.is_negative() { '-' } else { '+' },
+ self.hours.abs(),
+ self.minutes.abs(),
+ self.seconds.abs()
+ )
+ }
+}
+
+impl fmt::Debug for UtcOffset {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+impl Neg for UtcOffset {
+ type Output = Self;
+
+ fn neg(self) -> Self::Output {
+ Self::__from_hms_unchecked(-self.hours, -self.minutes, -self.seconds)
+ }
+}
diff --git a/third_party/rust/time/src/util.rs b/third_party/rust/time/src/util.rs
new file mode 100644
index 0000000000..11330501ca
--- /dev/null
+++ b/third_party/rust/time/src/util.rs
@@ -0,0 +1,31 @@
+//! Utility functions.
+
+pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year};
+
+use crate::Month;
+
+/// Whether to adjust the date, and in which direction. Useful when implementing arithmetic.
+pub(crate) enum DateAdjustment {
+ /// The previous day should be used.
+ Previous,
+ /// The next day should be used.
+ Next,
+ /// The date should be used as-is.
+ None,
+}
+
+/// Get the number of days in the month of a given year.
+///
+/// ```rust
+/// # use time::{Month, util};
+/// assert_eq!(util::days_in_year_month(2020, Month::February), 29);
+/// ```
+pub const fn days_in_year_month(year: i32, month: Month) -> u8 {
+ use Month::*;
+ match month {
+ January | March | May | July | August | October | December => 31,
+ April | June | September | November => 30,
+ February if is_leap_year(year) => 29,
+ February => 28,
+ }
+}
diff --git a/third_party/rust/time/src/weekday.rs b/third_party/rust/time/src/weekday.rs
new file mode 100644
index 0000000000..f499c30eef
--- /dev/null
+++ b/third_party/rust/time/src/weekday.rs
@@ -0,0 +1,148 @@
+//! Days of the week.
+
+use core::fmt::{self, Display};
+use core::str::FromStr;
+
+use Weekday::*;
+
+use crate::error;
+
+/// Days of the week.
+///
+/// As order is dependent on context (Sunday could be either two days after or five days before
+/// Friday), this type does not implement `PartialOrd` or `Ord`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Weekday {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Monday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Tuesday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Wednesday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Thursday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Friday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Saturday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Sunday,
+}
+
+impl Weekday {
+ /// Get the previous weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Tuesday.previous(), Weekday::Monday);
+ /// ```
+ pub const fn previous(self) -> Self {
+ match self {
+ Monday => Sunday,
+ Tuesday => Monday,
+ Wednesday => Tuesday,
+ Thursday => Wednesday,
+ Friday => Thursday,
+ Saturday => Friday,
+ Sunday => Saturday,
+ }
+ }
+
+ /// Get the next weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.next(), Weekday::Tuesday);
+ /// ```
+ pub const fn next(self) -> Self {
+ match self {
+ Monday => Tuesday,
+ Tuesday => Wednesday,
+ Wednesday => Thursday,
+ Thursday => Friday,
+ Friday => Saturday,
+ Saturday => Sunday,
+ Sunday => Monday,
+ }
+ }
+
+ /// Get the one-indexed number of days from Monday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_from_monday(), 1);
+ /// ```
+ #[doc(alias = "iso_weekday_number")]
+ pub const fn number_from_monday(self) -> u8 {
+ self.number_days_from_monday() + 1
+ }
+
+ /// Get the one-indexed number of days from Sunday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_from_sunday(), 2);
+ /// ```
+ pub const fn number_from_sunday(self) -> u8 {
+ self.number_days_from_sunday() + 1
+ }
+
+ /// Get the zero-indexed number of days from Monday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_days_from_monday(), 0);
+ /// ```
+ pub const fn number_days_from_monday(self) -> u8 {
+ self as _
+ }
+
+ /// Get the zero-indexed number of days from Sunday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_days_from_sunday(), 1);
+ /// ```
+ pub const fn number_days_from_sunday(self) -> u8 {
+ match self {
+ Monday => 1,
+ Tuesday => 2,
+ Wednesday => 3,
+ Thursday => 4,
+ Friday => 5,
+ Saturday => 6,
+ Sunday => 0,
+ }
+ }
+}
+
+impl Display for Weekday {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ Monday => "Monday",
+ Tuesday => "Tuesday",
+ Wednesday => "Wednesday",
+ Thursday => "Thursday",
+ Friday => "Friday",
+ Saturday => "Saturday",
+ Sunday => "Sunday",
+ })
+ }
+}
+
+impl FromStr for Weekday {
+ type Err = error::InvalidVariant;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "Monday" => Ok(Monday),
+ "Tuesday" => Ok(Tuesday),
+ "Wednesday" => Ok(Wednesday),
+ "Thursday" => Ok(Thursday),
+ "Friday" => Ok(Friday),
+ "Saturday" => Ok(Saturday),
+ "Sunday" => Ok(Sunday),
+ _ => Err(error::InvalidVariant),
+ }
+ }
+}