summaryrefslogtreecommitdiffstats
path: root/third_party/rust/time
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/time
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/time')
-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.md51
-rw-r--r--third_party/rust/time/src/date.rs1223
-rw-r--r--third_party/rust/time/src/date_time.rs1180
-rw-r--r--third_party/rust/time/src/duration.rs1418
-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.rs128
-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.rs280
-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.rs41
-rw-r--r--third_party/rust/time/src/format_description/mod.rs36
-rw-r--r--third_party/rust/time/src/format_description/modifier.rs409
-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.rs383
-rw-r--r--third_party/rust/time/src/format_description/parse/format_item.rs542
-rw-r--r--third_party/rust/time/src/format_description/parse/lexer.rs299
-rw-r--r--third_party/rust/time/src/format_description/parse/mod.rs245
-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.rs250
-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.rs307
-rw-r--r--third_party/rust/time/src/formatting/iso8601.rs140
-rw-r--r--third_party/rust/time/src/formatting/mod.rs545
-rw-r--r--third_party/rust/time/src/instant.rs277
-rw-r--r--third_party/rust/time/src/lib.rs373
-rw-r--r--third_party/rust/time/src/macros.rs132
-rw-r--r--third_party/rust/time/src/month.rs218
-rw-r--r--third_party/rust/time/src/offset_date_time.rs1196
-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.rs333
-rw-r--r--third_party/rust/time/src/parsing/iso8601.rs322
-rw-r--r--third_party/rust/time/src/parsing/mod.rs50
-rw-r--r--third_party/rust/time/src/parsing/parsable.rs760
-rw-r--r--third_party/rust/time/src/parsing/parsed.rs840
-rw-r--r--third_party/rust/time/src/parsing/shim.rs50
-rw-r--r--third_party/rust/time/src/primitive_date_time.rs885
-rw-r--r--third_party/rust/time/src/quickcheck.rs232
-rw-r--r--third_party/rust/time/src/rand.rs100
-rw-r--r--third_party/rust/time/src/serde/iso8601.rs77
-rw-r--r--third_party/rust/time/src/serde/mod.rs495
-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.rs323
-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.rs28
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/unix.rs157
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/wasm_js.rs13
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/windows.rs110
-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.rs777
-rw-r--r--third_party/rust/time/src/utc_offset.rs355
-rw-r--r--third_party/rust/time/src/util.rs99
-rw-r--r--third_party/rust/time/src/weekday.rs192
72 files changed, 18307 insertions, 0 deletions
diff --git a/third_party/rust/time/.cargo-checksum.json b/third_party/rust/time/.cargo-checksum.json
new file mode 100644
index 0000000000..3b5423f531
--- /dev/null
+++ b/third_party/rust/time/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"a011e69d5ae4484afdc3df407f6fcfdc91461b412730302a3d4fb4f4f731fe3f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","README.md":"91fdb919a41d2c1ee240a66d548adf1e1a40db95bcfe3adaf8c55f37c2afe1e9","src/date.rs":"5c0ae4a98530dd954c19b972df517b80bd6f35c73747e422b55ea0162d4c5fcf","src/date_time.rs":"37c97a93ca15be9b9bbd64b497429c430e91b6bcf51a50f571f79646b4348707","src/duration.rs":"469e62e0b545f45b99c37a7033c269474a932b52a2dc9399d62bf7877be1a8ce","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":"cf617348b55d9c3273060fa2d99bd4eda215452270025f2b6caef6ef9f387af5","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":"f31cdcf38c23a0111524ae431420f299d4d4735d99fc9a873d3472a3699de7ef","src/format_description/borrowed_format_item.rs":"afab66e65a84895751d3557fc5b8a3a5e63f9c483a6a534aa4f86fd2a5145f0b","src/format_description/component.rs":"289469371588f24de6c7afdd40e7ce65f6b08c3e05434900eafdca7dde59ab07","src/format_description/mod.rs":"955a227e9bb13e3085a43457bf8028085db92c0266b6573ddf1e12df3b937c0f","src/format_description/modifier.rs":"5c6330b3557a156d2acfd4eb454783a41a6edf62c5046e2ca60dc060caf31451","src/format_description/owned_format_item.rs":"419f5354bf504562c9225dfe90b61eee9bc959211a86a327197b4f54283da775","src/format_description/parse/ast.rs":"c20fedf0314a2ede5e3ee66024534ca8a65a2b623959386872250e19d5d43b6e","src/format_description/parse/format_item.rs":"4639e23fb86dbbef6d764e8279cc43dd5f6e09d8b14b277e6f6b9bce81f5c3ff","src/format_description/parse/lexer.rs":"c10105640a618e1e850eb6e4fd888c47d881b3f85bde691fdf204199a693e127","src/format_description/parse/mod.rs":"210cd68a37b5cbbc6a6e3b3d5161f03ad94b2902bb01899d0c02d0278f420c8c","src/format_description/well_known/iso8601.rs":"8313905039a637d4d132f8318a59c06246e7b61550b4e4bc7d129232ac022e43","src/format_description/well_known/iso8601/adt_hack.rs":"59a5182dc200a26654944a64a81488a55c7a387485f219371503a010c751e338","src/format_description/well_known/rfc2822.rs":"36c23394724ae12250d4193cab26887a6ff8f82ca441ea6b0d03c4f1c928b3dd","src/format_description/well_known/rfc3339.rs":"1a6318dffd3ebb6ac7cf96eae3d9b1eb44b1089cf4284fa6a7e935c6fcf1b43c","src/formatting/formattable.rs":"fe75a835d20f144faf8f1297d9b501e72fcce321c7dc1077805e4a2b2f9b9390","src/formatting/iso8601.rs":"3dc83bf234b60e80ab499bf3ec490b2772d69a02b452f93cbc8e843ebf340fc2","src/formatting/mod.rs":"b4f98455609968a28e6479077d01eec60e3331064dbcd453f29c6d5a768d9857","src/instant.rs":"f1724e49b173b16b08818bfd06133ce4f61da7df286ff61982113cc184efe1c0","src/lib.rs":"be86048ca1c4ab497384edd9507b41c5683d946b3149ff978277c1323cbc2889","src/macros.rs":"eb9e02a1f97bb8befab7bc27c937136817e4f65e0b3e040a81394ae938980558","src/month.rs":"91b20ea12b1a36a9ddb828279572c6098b8504a048627088137963a612b572b8","src/offset_date_time.rs":"288d7a34eecbbd9345e13804302bcb55df716fdef0fafe1d2d06e52c0d77829e","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":"09008cf9d08c4b0c3cb051b986291a29e977a780bb1455f9c33e472db983e8da","src/parsing/iso8601.rs":"8dd99317a6fcde7d85f8bf5c492675efde751731b687b0cde0d5653cb129743a","src/parsing/mod.rs":"37082ac824c6c3f4900766a0a3140dc7aa46b3f85cb6098f11da7da333e421b0","src/parsing/parsable.rs":"d1b3c001f57c735af395553d35e76f9342a83da87b5a843d1eb015807a076db9","src/parsing/parsed.rs":"5cafd2220fe325817b8b65729274a0ca7c741f4579d99bc888cb1997436ef127","src/parsing/shim.rs":"46efc374bc3129e28936a850143fff8e42aafe10c69ebbb904195aaeca26adc9","src/primitive_date_time.rs":"ce557a9db6d7ed663ff78d62a60af5ee8a287f04f6fc979e81047c339d50495a","src/quickcheck.rs":"94640161a21319b75c9b31a6bbc6e34a4d573c20606b954db1bd12ddef366af8","src/rand.rs":"889c98938427a4885036673df8fcebd84c7cc20fb4b3ca82c447ff4b977c8a15","src/serde/iso8601.rs":"997bbf4fe4018f8fdc9335ac863b543fb24a58b2dee394615505a24311331516","src/serde/mod.rs":"a575be28adaf89ff5b1476b367e2019ea09d68368f227d211bc6f33f192bc1e3","src/serde/rfc2822.rs":"fe97aa1311037a362eb477fe8c6729b3b85ff2d0afab7148f10f64d109081f90","src/serde/rfc3339.rs":"9835c8b8fb24b53657769b81a71188fe4261e5869917779e1702b3a0aa854654","src/serde/timestamp.rs":"30971ad5d1fef11e396eee48d476b828ed4e99f6eac587383b864dd95c120fe4","src/serde/visitor.rs":"c5293181f337ab09ae98ce4ef41eae558ae5e3b86f961e4a0c9c93cb034647ed","src/sys/local_offset_at/imp.rs":"4b6e57f02566364270ac9b7e1540290a5658a296f7e911f988264d103e420326","src/sys/local_offset_at/mod.rs":"95b042824b414b3021eda2bcf0821afc529bfd8d4cfcad0b893edb197e48461b","src/sys/local_offset_at/unix.rs":"339ab502e121c24c6ea617f444a58fb7e23cf5afd13c5f7a52eda6d69591d580","src/sys/local_offset_at/wasm_js.rs":"e49ef256c874d6b8d15ef264a66c0b837ac42cd0683e38f3f31af2c2e8fca459","src/sys/local_offset_at/windows.rs":"0836e20249421b1f32e77f0ce4be0d3db30be00478f4c56fda9ddbff0bbb0c5d","src/sys/mod.rs":"0a43797e55e986233a71f1cc4b3a21997da42bc15db7d912373296cd535e49bc","src/tests.rs":"38d1f794892e6ab3fece55839a8e4ab6d0d2c325323310eda32144eb7240bf59","src/time.rs":"197c53ef2b49f73c363eabe2332ffd4eaba18f91f2d17070e8d568069a977c64","src/utc_offset.rs":"ce39c34ec5419a1bf51f7b8401e38a4e0daab7e827fe2fd239fae8089a212c7e","src/util.rs":"1fff6c7d712a4d2665cca55db9c142185cc13afa20f925912cb85abbcc366938","src/weekday.rs":"0a9f79b6aef6bb085204216d0be1c7666426c589c3263b63384c4b74e8b54658"},"package":"59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"} \ 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..ad2e8c94b3
--- /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.65.0"
+name = "time"
+version = "0.3.23"
+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
+rustdoc-args = [
+ "--cfg",
+ "__time_03_docs",
+]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[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.1"
+
+[dependencies.time-macros]
+version = "=0.2.10"
+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.10"
+
+[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_family = \"wasm\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys]
+version = "0.3.58"
+optional = true
+
+[target."cfg(bench)".dev-dependencies.criterion]
+version = "0.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..173b3c4a2b
--- /dev/null
+++ b/third_party/rust/time/README.md
@@ -0,0 +1,51 @@
+# time
+
+[![minimum rustc: 1.65](https://img.shields.io/badge/minimum%20rustc-1.65-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
+[![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time)
+[![build status](https://img.shields.io/github/actions/workflow/status/time-rs/time/build.yaml?branch=main&style=flat-square)](https://github.com/time-rs/time/actions)
+[![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time)
+
+Documentation:
+
+- [latest release](https://docs.rs/time)
+- [main branch](https://time-rs.github.io/api/time)
+- [book](https://time-rs.github.io/book)
+
+## Minimum Rust version policy
+
+`time` is guaranteed to compile with the latest stable release of Rust in addition to the two prior
+minor releases. For example, if the latest stable Rust release is 1.70, then `time` is guaranteed to
+compile with Rust 1.68, 1.69, and 1.70.
+
+The minimum supported Rust version may be increased to one of the aforementioned versions if doing
+so provides the end user a benefit. However, the minimum supported Rust version may also be bumped
+to a version four minor releases prior to the most recent stable release if doing so improves code
+quality or maintainability.
+
+For interoperability with third-party crates, it is guaranteed that there exists a version of that
+crate that supports the minimum supported Rust version of `time`. This does not mean that the latest
+version of the third-party crate supports the minimum supported Rust version of `time`.
+
+## Contributing
+
+Contributions are always welcome! If you have an idea, it's best to float it by me before working on
+it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out.
+Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time).
+
+If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's
+what I'm here for!
+
+[Discussions]: https://github.com/time-rs/time/discussions
+
+## License
+
+This project is licensed under either of
+
+- [Apache License, Version 2.0](https://github.com/time-rs/time/blob/main/LICENSE-Apache)
+- [MIT license](https://github.com/time-rs/time/blob/main/LICENSE-MIT)
+
+at your option.
+
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
+time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/third_party/rust/time/src/date.rs b/third_party/rust/time/src/date.rs
new file mode 100644
index 0000000000..ad8565a8e1
--- /dev/null
+++ b/third_party/rust/time/src/date.rs
@@ -0,0 +1,1223 @@
+//! 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;
+
+use crate::convert::*;
+#[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),
+ ))
+ }
+ }
+
+ /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
+ ///
+ /// # Panics
+ /// Panics if an overflow occurred.
+ ///
+ /// # Examples
+ /// ```
+ /// # use time::Weekday;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2023 - 06 - 28).next_occurrence(Weekday::Monday),
+ /// date!(2023 - 07 - 03)
+ /// );
+ /// assert_eq!(
+ /// date!(2023 - 06 - 19).next_occurrence(Weekday::Monday),
+ /// date!(2023 - 06 - 26)
+ /// );
+ /// ```
+ pub const fn next_occurrence(self, weekday: Weekday) -> Self {
+ expect_opt!(
+ self.checked_next_occurrence(weekday),
+ "overflow calculating the next occurrence of a weekday"
+ )
+ }
+
+ /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`.
+ ///
+ /// # Panics
+ /// Panics if an overflow occurred.
+ ///
+ /// # Examples
+ /// ```
+ /// # use time::Weekday;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2023 - 06 - 28).prev_occurrence(Weekday::Monday),
+ /// date!(2023 - 06 - 26)
+ /// );
+ /// assert_eq!(
+ /// date!(2023 - 06 - 19).prev_occurrence(Weekday::Monday),
+ /// date!(2023 - 06 - 12)
+ /// );
+ /// ```
+ pub const fn prev_occurrence(self, weekday: Weekday) -> Self {
+ expect_opt!(
+ self.checked_prev_occurrence(weekday),
+ "overflow calculating the previous occurrence of a weekday"
+ )
+ }
+
+ /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`.
+ ///
+ /// # Panics
+ /// Panics if an overflow occurred or if `n == 0`.
+ ///
+ /// # Examples
+ /// ```
+ /// # use time::Weekday;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 5),
+ /// date!(2023 - 07 - 24)
+ /// );
+ /// assert_eq!(
+ /// date!(2023 - 06 - 26).nth_next_occurrence(Weekday::Monday, 5),
+ /// date!(2023 - 07 - 31)
+ /// );
+ /// ```
+ pub const fn nth_next_occurrence(self, weekday: Weekday, n: u8) -> Self {
+ expect_opt!(
+ self.checked_nth_next_occurrence(weekday, n),
+ "overflow calculating the next occurrence of a weekday"
+ )
+ }
+
+ /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`.
+ ///
+ /// # Panics
+ /// Panics if an overflow occurred or if `n == 0`.
+ ///
+ /// # Examples
+ /// ```
+ /// # use time::Weekday;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 3),
+ /// date!(2023 - 06 - 12)
+ /// );
+ /// assert_eq!(
+ /// date!(2023 - 06 - 26).nth_prev_occurrence(Weekday::Monday, 3),
+ /// date!(2023 - 06 - 05)
+ /// );
+ /// ```
+ pub const fn nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Self {
+ expect_opt!(
+ self.checked_nth_prev_occurrence(weekday, n),
+ "overflow calculating the previous occurrence of a weekday"
+ )
+ }
+
+ /// 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
+ }
+ }
+
+ /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
+ /// Returns `None` if an overflow occurred.
+ pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option<Self> {
+ let day_diff = match weekday as i8 - self.weekday() as i8 {
+ 1 | -6 => 1,
+ 2 | -5 => 2,
+ 3 | -4 => 3,
+ 4 | -3 => 4,
+ 5 | -2 => 5,
+ 6 | -1 => 6,
+ val => {
+ debug_assert!(val == 0);
+ 7
+ }
+ };
+
+ self.checked_add(Duration::days(day_diff))
+ }
+
+ /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`.
+ /// Returns `None` if an overflow occurred.
+ pub(crate) const fn checked_prev_occurrence(self, weekday: Weekday) -> Option<Self> {
+ let day_diff = match weekday as i8 - self.weekday() as i8 {
+ 1 | -6 => 6,
+ 2 | -5 => 5,
+ 3 | -4 => 4,
+ 4 | -3 => 3,
+ 5 | -2 => 2,
+ 6 | -1 => 1,
+ val => {
+ debug_assert!(val == 0);
+ 7
+ }
+ };
+
+ self.checked_sub(Duration::days(day_diff))
+ }
+
+ /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`.
+ /// Returns `None` if an overflow occurred or if `n == 0`.
+ pub(crate) const fn checked_nth_next_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> {
+ if n == 0 {
+ return None;
+ }
+
+ let next_occ = self.checked_next_occurrence(weekday);
+ if let Some(val) = next_occ {
+ val.checked_add(Duration::weeks(n as i64 - 1))
+ } else {
+ None
+ }
+ }
+
+ /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`.
+ /// Returns `None` if an overflow occurred or if `n == 0`.
+ pub(crate) const fn checked_nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> {
+ if n == 0 {
+ return None;
+ }
+
+ let next_occ = self.checked_prev_occurrence(weekday);
+ if let Some(val) = next_occ {
+ val.checked_sub(Duration::weeks(n as i64 - 1))
+ } else {
+ None
+ }
+ }
+ // 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() / Second.per(Day) as u64) 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() / Second.per(Day) as u64) 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/date_time.rs b/third_party/rust/time/src/date_time.rs
new file mode 100644
index 0000000000..f4730f502d
--- /dev/null
+++ b/third_party/rust/time/src/date_time.rs
@@ -0,0 +1,1180 @@
+//! The [`DateTime`] struct and its associated `impl`s.
+
+// TODO(jhpratt) Document everything before making public.
+#![allow(clippy::missing_docs_in_private_items)]
+// This is intentional, as the struct will likely be exposed at some point.
+#![allow(unreachable_pub)]
+
+use core::cmp::Ordering;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::mem::size_of;
+use core::ops::{Add, AddAssign, Sub, SubAssign};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+#[cfg(feature = "std")]
+use std::time::SystemTime;
+
+use crate::convert::*;
+use crate::date::{MAX_YEAR, MIN_YEAR};
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::{Parsable, Parsed};
+use crate::{error, util, Date, Duration, Month, Time, UtcOffset, Weekday};
+
+#[allow(missing_debug_implementations, missing_copy_implementations)]
+pub(crate) mod offset_kind {
+ pub enum None {}
+ pub enum Fixed {}
+}
+
+pub(crate) use sealed::MaybeOffset;
+use sealed::*;
+mod sealed {
+ use super::*;
+
+ /// A type that is guaranteed to be either `()` or [`UtcOffset`].
+ ///
+ /// **Do not** add any additional implementations of this trait.
+ #[allow(unreachable_pub)] // intentional
+ pub trait MaybeOffsetType {}
+ impl MaybeOffsetType for () {}
+ impl MaybeOffsetType for UtcOffset {}
+
+ pub trait MaybeOffset: Sized {
+ /// The offset type as it is stored in memory.
+ #[cfg(feature = "quickcheck")]
+ type MemoryOffsetType: Copy + MaybeOffsetType + quickcheck::Arbitrary;
+ #[cfg(not(feature = "quickcheck"))]
+ type MemoryOffsetType: Copy + MaybeOffsetType;
+
+ /// The offset type as it should be thought about.
+ ///
+ /// For example, a `DateTime<Utc>` has a logical offset type of [`UtcOffset`], but does not
+ /// actually store an offset in memory.
+ type LogicalOffsetType: Copy + MaybeOffsetType;
+
+ /// Required to be `Self`. Used for bound equality.
+ type Self_;
+
+ /// True if and only if `Self::LogicalOffsetType` is `UtcOffset`.
+ const HAS_LOGICAL_OFFSET: bool =
+ size_of::<Self::LogicalOffsetType>() == size_of::<UtcOffset>();
+ /// True if and only if `Self::MemoryOffsetType` is `UtcOffset`.
+ const HAS_MEMORY_OFFSET: bool =
+ size_of::<Self::MemoryOffsetType>() == size_of::<UtcOffset>();
+
+ /// `Some` if and only if the logical UTC offset is statically known.
+ // TODO(jhpratt) When const trait impls are stable, this can be removed in favor of
+ // `.as_offset_opt()`.
+ const STATIC_OFFSET: Option<UtcOffset>;
+
+ #[cfg(feature = "parsing")]
+ fn try_from_parsed(parsed: Parsed) -> Result<Self::MemoryOffsetType, error::TryFromParsed>;
+ }
+
+ // Traits to indicate whether a `MaybeOffset` has a logical offset type of `UtcOffset` or not.
+
+ pub trait HasLogicalOffset: MaybeOffset<LogicalOffsetType = UtcOffset> {}
+ impl<T: MaybeOffset<LogicalOffsetType = UtcOffset>> HasLogicalOffset for T {}
+
+ pub trait NoLogicalOffset: MaybeOffset<LogicalOffsetType = ()> {}
+ impl<T: MaybeOffset<LogicalOffsetType = ()>> NoLogicalOffset for T {}
+
+ // Traits to indicate whether a `MaybeOffset` has a memory offset type of `UtcOffset` or not.
+
+ pub trait HasMemoryOffset: MaybeOffset<MemoryOffsetType = UtcOffset> {}
+ impl<T: MaybeOffset<MemoryOffsetType = UtcOffset>> HasMemoryOffset for T {}
+
+ pub trait NoMemoryOffset: MaybeOffset<MemoryOffsetType = ()> {}
+ impl<T: MaybeOffset<MemoryOffsetType = ()>> NoMemoryOffset for T {}
+
+ // Traits to indicate backing type being implemented.
+
+ pub trait IsOffsetKindNone:
+ MaybeOffset<Self_ = offset_kind::None, MemoryOffsetType = (), LogicalOffsetType = ()>
+ {
+ }
+ impl IsOffsetKindNone for offset_kind::None {}
+
+ pub trait IsOffsetKindFixed:
+ MaybeOffset<
+ Self_ = offset_kind::Fixed,
+ MemoryOffsetType = UtcOffset,
+ LogicalOffsetType = UtcOffset,
+ >
+ {
+ }
+ impl IsOffsetKindFixed for offset_kind::Fixed {}
+}
+
+impl MaybeOffset for offset_kind::None {
+ type MemoryOffsetType = ();
+ type LogicalOffsetType = ();
+
+ type Self_ = Self;
+
+ const STATIC_OFFSET: Option<UtcOffset> = None;
+
+ #[cfg(feature = "parsing")]
+ fn try_from_parsed(_: Parsed) -> Result<(), error::TryFromParsed> {
+ Ok(())
+ }
+}
+
+impl MaybeOffset for offset_kind::Fixed {
+ type MemoryOffsetType = UtcOffset;
+ type LogicalOffsetType = UtcOffset;
+
+ type Self_ = Self;
+
+ const STATIC_OFFSET: Option<UtcOffset> = None;
+
+ #[cfg(feature = "parsing")]
+ fn try_from_parsed(parsed: Parsed) -> Result<UtcOffset, error::TryFromParsed> {
+ parsed.try_into()
+ }
+}
+
+// region: const trait method hacks
+// TODO(jhpratt) When const trait impls are stable, these methods can be removed in favor of methods
+// in `MaybeOffset`, which would then be made `const`.
+const fn maybe_offset_as_offset_opt<O: MaybeOffset>(
+ offset: O::MemoryOffsetType,
+) -> Option<UtcOffset> {
+ if O::STATIC_OFFSET.is_some() {
+ O::STATIC_OFFSET
+ } else if O::HAS_MEMORY_OFFSET {
+ #[repr(C)] // needed to guarantee they align at the start
+ union Convert<O: MaybeOffset> {
+ input: O::MemoryOffsetType,
+ output: UtcOffset,
+ }
+
+ // Safety: `O::HAS_OFFSET` indicates that `O::Offset` is `UtcOffset`. This code effectively
+ // performs a transmute from `O::Offset` to `UtcOffset`, which we know is the same type.
+ Some(unsafe { Convert::<O> { input: offset }.output })
+ } else {
+ None
+ }
+}
+
+const fn maybe_offset_as_offset<O: MaybeOffset + HasLogicalOffset>(
+ offset: O::MemoryOffsetType,
+) -> UtcOffset {
+ match maybe_offset_as_offset_opt::<O>(offset) {
+ Some(offset) => offset,
+ None => bug!("`MaybeOffset::as_offset` called on a type without an offset in memory"),
+ }
+}
+
+pub(crate) const fn maybe_offset_from_offset<O: MaybeOffset>(
+ offset: UtcOffset,
+) -> O::MemoryOffsetType {
+ #[repr(C)] // needed to guarantee the types align at the start
+ union Convert<O: MaybeOffset> {
+ input: UtcOffset,
+ output: O::MemoryOffsetType,
+ }
+
+ // Safety: It is statically known that there are only two possibilities due to the trait bound
+ // of `O::MemoryOffsetType`, which ultimately relies on `MaybeOffsetType`. The two possibilities
+ // are:
+ // 1. UtcOffset -> UtcOffset
+ // 2. UtcOffset -> ()
+ // (1) is valid because it is an identity conversion, which is always valid. (2) is valid
+ // because `()` is a 1-ZST, so converting to it is always valid.
+ unsafe { Convert::<O> { input: offset }.output }
+}
+// endregion const trait methods hacks
+
+/// The Julian day of the Unix epoch.
+const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day();
+
+pub struct DateTime<O: MaybeOffset> {
+ pub(crate) date: Date,
+ pub(crate) time: Time,
+ pub(crate) offset: O::MemoryOffsetType,
+}
+
+// Manual impl to remove extraneous bounds.
+impl<O: MaybeOffset> Clone for DateTime<O> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+// Manual impl to remove extraneous bounds.
+impl<O: MaybeOffset> Copy for DateTime<O> {}
+
+// region: constructors
+impl DateTime<offset_kind::None> {
+ pub const MIN: Self = Self {
+ date: Date::MIN,
+ time: Time::MIN,
+ offset: (),
+ };
+
+ pub const MAX: Self = Self {
+ date: Date::MAX,
+ time: Time::MAX,
+ offset: (),
+ };
+}
+
+impl DateTime<offset_kind::Fixed> {
+ pub const UNIX_EPOCH: Self = Self {
+ date: Date::__from_ordinal_date_unchecked(1970, 1),
+ time: Time::MIDNIGHT,
+ offset: UtcOffset::UTC,
+ };
+}
+
+impl<O: MaybeOffset> DateTime<O> {
+ pub const fn new(date: Date, time: Time) -> Self
+ where
+ O: IsOffsetKindNone,
+ {
+ Self {
+ date,
+ time,
+ offset: (),
+ }
+ }
+
+ pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange>
+ where
+ O: HasLogicalOffset,
+ {
+ #[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, Second.per(Day) as i64) as i32,
+ );
+
+ let seconds_within_day = timestamp.rem_euclid(Second.per(Day) as _);
+ let time = Time::__from_hms_nanos_unchecked(
+ (seconds_within_day / Second.per(Hour) as i64) as _,
+ ((seconds_within_day % Second.per(Hour) as i64) / Minute.per(Hour) as i64) as _,
+ (seconds_within_day % Second.per(Minute) as i64) as _,
+ 0,
+ );
+
+ Ok(Self {
+ date,
+ time,
+ offset: maybe_offset_from_offset::<O>(UtcOffset::UTC),
+ })
+ }
+
+ pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange>
+ where
+ O: HasLogicalOffset,
+ {
+ let datetime = const_try!(Self::from_unix_timestamp(div_floor!(
+ timestamp,
+ Nanosecond.per(Second) as i128
+ ) as i64));
+
+ Ok(Self {
+ date: datetime.date,
+ time: Time::__from_hms_nanos_unchecked(
+ datetime.hour(),
+ datetime.minute(),
+ datetime.second(),
+ timestamp.rem_euclid(Nanosecond.per(Second) as _) as u32,
+ ),
+ offset: maybe_offset_from_offset::<O>(UtcOffset::UTC),
+ })
+ }
+ // endregion constructors
+
+ // region: now
+ // The return type will likely be loosened once `ZonedDateTime` is implemented. This is not a
+ // breaking change calls are currently limited to only `OffsetDateTime`.
+ #[cfg(feature = "std")]
+ pub fn now_utc() -> DateTime<offset_kind::Fixed>
+ where
+ O: IsOffsetKindFixed,
+ {
+ #[cfg(all(
+ target_family = "wasm",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ ))]
+ {
+ js_sys::Date::new_0().into()
+ }
+
+ #[cfg(not(all(
+ target_family = "wasm",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ )))]
+ SystemTime::now().into()
+ }
+
+ // The return type will likely be loosened once `ZonedDateTime` is implemented. This is not a
+ // breaking change calls are currently limited to only `OffsetDateTime`.
+ #[cfg(feature = "local-offset")]
+ pub fn now_local() -> Result<DateTime<offset_kind::Fixed>, error::IndeterminateOffset>
+ where
+ O: IsOffsetKindFixed,
+ {
+ let t = DateTime::<offset_kind::Fixed>::now_utc();
+ Ok(t.to_offset(UtcOffset::local_offset_at(crate::OffsetDateTime(t))?))
+ }
+ // endregion now
+
+ // region: getters
+ // region: component getters
+ pub const fn date(self) -> Date {
+ self.date
+ }
+
+ pub const fn time(self) -> Time {
+ self.time
+ }
+
+ pub const fn offset(self) -> UtcOffset
+ where
+ O: HasLogicalOffset,
+ {
+ maybe_offset_as_offset::<O>(self.offset)
+ }
+ // endregion component getters
+
+ // region: date getters
+ pub const fn year(self) -> i32 {
+ self.date.year()
+ }
+
+ pub const fn month(self) -> Month {
+ self.date.month()
+ }
+
+ pub const fn day(self) -> u8 {
+ self.date.day()
+ }
+
+ pub const fn ordinal(self) -> u16 {
+ self.date.ordinal()
+ }
+
+ pub const fn iso_week(self) -> u8 {
+ self.date.iso_week()
+ }
+
+ pub const fn sunday_based_week(self) -> u8 {
+ self.date.sunday_based_week()
+ }
+
+ pub const fn monday_based_week(self) -> u8 {
+ self.date.monday_based_week()
+ }
+
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ self.date.to_calendar_date()
+ }
+
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ self.date.to_ordinal_date()
+ }
+
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ self.date.to_iso_week_date()
+ }
+
+ pub const fn weekday(self) -> Weekday {
+ self.date.weekday()
+ }
+
+ pub const fn to_julian_day(self) -> i32 {
+ self.date.to_julian_day()
+ }
+ // endregion date getters
+
+ // region: time getters
+ pub const fn as_hms(self) -> (u8, u8, u8) {
+ self.time.as_hms()
+ }
+
+ pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
+ self.time.as_hms_milli()
+ }
+
+ pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_micro()
+ }
+
+ pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_nano()
+ }
+
+ pub const fn hour(self) -> u8 {
+ self.time.hour()
+ }
+
+ pub const fn minute(self) -> u8 {
+ self.time.minute()
+ }
+
+ pub const fn second(self) -> u8 {
+ self.time.second()
+ }
+
+ pub const fn millisecond(self) -> u16 {
+ self.time.millisecond()
+ }
+
+ pub const fn microsecond(self) -> u32 {
+ self.time.microsecond()
+ }
+
+ pub const fn nanosecond(self) -> u32 {
+ self.time.nanosecond()
+ }
+ // endregion time getters
+
+ // region: unix timestamp getters
+ pub const fn unix_timestamp(self) -> i64
+ where
+ O: HasLogicalOffset,
+ {
+ let offset = maybe_offset_as_offset::<O>(self.offset).whole_seconds() as i64;
+
+ let days =
+ (self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second.per(Day) as i64;
+ let hours = self.hour() as i64 * Second.per(Hour) as i64;
+ let minutes = self.minute() as i64 * Second.per(Minute) as i64;
+ let seconds = self.second() as i64;
+ days + hours + minutes + seconds - offset
+ }
+
+ pub const fn unix_timestamp_nanos(self) -> i128
+ where
+ O: HasLogicalOffset,
+ {
+ self.unix_timestamp() as i128 * Nanosecond.per(Second) as i128 + self.nanosecond() as i128
+ }
+ // endregion unix timestamp getters
+ // endregion: getters
+
+ // region: attach offset
+ pub const fn assume_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed>
+ where
+ O: NoLogicalOffset,
+ {
+ DateTime {
+ date: self.date,
+ time: self.time,
+ offset,
+ }
+ }
+
+ pub const fn assume_utc(self) -> DateTime<offset_kind::Fixed>
+ where
+ O: NoLogicalOffset,
+ {
+ self.assume_offset(UtcOffset::UTC)
+ }
+ // endregion attach offset
+
+ // region: to offset
+ pub const fn to_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed>
+ where
+ O: HasLogicalOffset,
+ {
+ expect_opt!(
+ self.checked_to_offset(offset),
+ "local datetime out of valid range"
+ )
+ }
+
+ pub const fn checked_to_offset(self, offset: UtcOffset) -> Option<DateTime<offset_kind::Fixed>>
+ where
+ O: HasLogicalOffset,
+ {
+ let self_offset = maybe_offset_as_offset::<O>(self.offset);
+
+ 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 Some(DateTime {
+ date: self.date,
+ time: self.time,
+ offset,
+ });
+ }
+
+ let (year, ordinal, time) = self.to_offset_raw(offset);
+
+ if year > MAX_YEAR || year < MIN_YEAR {
+ return None;
+ }
+
+ Some(DateTime {
+ date: Date::__from_ordinal_date_unchecked(year, ordinal),
+ time,
+ 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.
+ pub(crate) const fn to_offset_raw(self, offset: UtcOffset) -> (i32, u16, Time) {
+ let Some(from) = maybe_offset_as_offset_opt::<O>(self.offset) else {
+ // No adjustment is needed because there is no offset.
+ return (self.year(), self.ordinal(), self.time);
+ };
+ 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..Second.per(Minute) as i16 => minute);
+ cascade!(second in 0..Second.per(Minute) as i16 => minute);
+ cascade!(minute in 0..Minute.per(Hour) as i16 => hour);
+ cascade!(minute in 0..Minute.per(Hour) as i16 => hour);
+ cascade!(hour in 0..Hour.per(Day) as i8 => ordinal);
+ cascade!(hour in 0..Hour.per(Day) as i8 => 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(),
+ ),
+ )
+ }
+ // endregion to offset
+
+ // region: checked arithmetic
+ 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,
+ offset: self.offset,
+ })
+ }
+
+ 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,
+ offset: self.offset,
+ })
+ }
+ // endregion checked arithmetic
+
+ // region: saturating arithmetic
+ pub const fn saturating_add(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_add(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self {
+ date: Date::MIN,
+ time: Time::MIN,
+ offset: self.offset,
+ }
+ } else {
+ Self {
+ date: Date::MAX,
+ time: Time::MAX,
+ offset: self.offset,
+ }
+ }
+ }
+
+ pub const fn saturating_sub(self, duration: Duration) -> Self {
+ if let Some(datetime) = self.checked_sub(duration) {
+ datetime
+ } else if duration.is_negative() {
+ Self {
+ date: Date::MAX,
+ time: Time::MAX,
+ offset: self.offset,
+ }
+ } else {
+ Self {
+ date: Date::MIN,
+ time: Time::MIN,
+ offset: self.offset,
+ }
+ }
+ }
+ // endregion saturating arithmetic
+
+ // region: replacement
+ pub const fn replace_time(self, time: Time) -> Self {
+ Self {
+ date: self.date,
+ time,
+ offset: self.offset,
+ }
+ }
+
+ pub const fn replace_date(self, date: Date) -> Self {
+ Self {
+ date,
+ time: self.time,
+ offset: self.offset,
+ }
+ }
+
+ pub const fn replace_date_time(self, date_time: DateTime<offset_kind::None>) -> Self
+ where
+ O: HasLogicalOffset,
+ {
+ Self {
+ date: date_time.date,
+ time: date_time.time,
+ offset: self.offset,
+ }
+ }
+
+ pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: const_try!(self.date.replace_year(year)),
+ time: self.time,
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: const_try!(self.date.replace_month(month)),
+ time: self.time,
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: const_try!(self.date.replace_day(day)),
+ time: self.time,
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_hour(hour)),
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_minute(minute)),
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_second(second)),
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_millisecond(millisecond)),
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_microsecond(microsecond)),
+ offset: self.offset,
+ })
+ }
+
+ pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
+ Ok(Self {
+ date: self.date,
+ time: const_try!(self.time.replace_nanosecond(nanosecond)),
+ offset: self.offset,
+ })
+ }
+
+ // Don't gate this on just having an offset, as `ZonedDateTime` cannot be set to an arbitrary
+ // offset.
+ pub const fn replace_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed>
+ where
+ O: IsOffsetKindFixed,
+ {
+ DateTime {
+ date: self.date,
+ time: self.time,
+ offset,
+ }
+ }
+
+ // endregion replacement
+
+ // region: formatting & parsing
+ #[cfg(feature = "formatting")]
+ 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),
+ maybe_offset_as_offset_opt::<O>(self.offset),
+ )
+ }
+
+ #[cfg(feature = "formatting")]
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(
+ Some(self.date),
+ Some(self.time),
+ maybe_offset_as_offset_opt::<O>(self.offset),
+ )
+ }
+
+ #[cfg(feature = "parsing")]
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_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.
+ #[cfg(feature = "parsing")]
+ pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool {
+ // Leap seconds aren't allowed if there is no offset.
+ if !O::HAS_LOGICAL_OFFSET {
+ return false;
+ }
+
+ // 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 Ok(date) = Date::from_ordinal_date(year, ordinal) else {
+ return false;
+ };
+
+ time.hour() == 23
+ && time.minute() == 59
+ && time.second() == 59
+ && date.day() == util::days_in_year_month(year, date.month())
+ }
+
+ // endregion formatting & parsing
+
+ // region: deprecated time getters
+
+ // All the way at the bottom as it's low priority. These methods only exist for when
+ // `OffsetDateTime` is made an alias of `DateTime<Fixed>`. Consider hiding these methods from
+ // documentation in the future.
+
+ #[doc(hidden)]
+ #[allow(dead_code)] // while functionally private
+ #[deprecated(since = "0.3.18", note = "use `as_hms` instead")]
+ pub const fn to_hms(self) -> (u8, u8, u8)
+ where
+ O: IsOffsetKindFixed,
+ {
+ self.time.as_hms()
+ }
+
+ #[doc(hidden)]
+ #[allow(dead_code)] // while functionally private
+ #[deprecated(since = "0.3.18", note = "use `as_hms_milli` instead")]
+ pub const fn to_hms_milli(self) -> (u8, u8, u8, u16)
+ where
+ O: IsOffsetKindFixed,
+ {
+ self.time.as_hms_milli()
+ }
+
+ #[doc(hidden)]
+ #[allow(dead_code)] // while functionally private
+ #[deprecated(since = "0.3.18", note = "use `as_hms_micro` instead")]
+ pub const fn to_hms_micro(self) -> (u8, u8, u8, u32)
+ where
+ O: IsOffsetKindFixed,
+ {
+ self.time.as_hms_micro()
+ }
+
+ #[doc(hidden)]
+ #[allow(dead_code)] // while functionally private
+ #[deprecated(since = "0.3.18", note = "use `as_hms_nano` instead")]
+ pub const fn to_hms_nano(self) -> (u8, u8, u8, u32)
+ where
+ O: IsOffsetKindFixed,
+ {
+ self.time.as_hms_nano()
+ }
+ // endregion deprecated time getters
+}
+
+impl<O: MaybeOffset> fmt::Debug for DateTime<O> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ <Self as fmt::Display>::fmt(self, f)
+ }
+}
+
+impl<O: MaybeOffset> fmt::Display for DateTime<O> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {}", self.date, self.time)?;
+ if let Some(offset) = maybe_offset_as_offset_opt::<O>(self.offset) {
+ write!(f, " {offset}")?;
+ }
+ Ok(())
+ }
+}
+
+// region: trait impls
+impl<O: MaybeOffset> PartialEq for DateTime<O> {
+ fn eq(&self, rhs: &Self) -> bool {
+ if O::HAS_LOGICAL_OFFSET {
+ self.to_offset_raw(UtcOffset::UTC) == rhs.to_offset_raw(UtcOffset::UTC)
+ } else {
+ (self.date, self.time) == (rhs.date, rhs.time)
+ }
+ }
+}
+
+impl<O: MaybeOffset> Eq for DateTime<O> {}
+
+impl<O: MaybeOffset> PartialOrd for DateTime<O> {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(self.cmp(rhs))
+ }
+}
+
+impl<O: MaybeOffset> Ord for DateTime<O> {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ if O::HAS_LOGICAL_OFFSET {
+ self.to_offset_raw(UtcOffset::UTC)
+ .cmp(&rhs.to_offset_raw(UtcOffset::UTC))
+ } else {
+ (self.date, self.time).cmp(&(rhs.date, rhs.time))
+ }
+ }
+}
+
+impl<O: MaybeOffset> Hash for DateTime<O> {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ if O::HAS_LOGICAL_OFFSET {
+ self.to_offset_raw(UtcOffset::UTC).hash(hasher);
+ } else {
+ (self.date, self.time).hash(hasher);
+ }
+ }
+}
+
+impl<O: MaybeOffset> Add<Duration> for DateTime<O> {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self {
+ self.checked_add(duration)
+ .expect("resulting value is out of range")
+ }
+}
+
+impl<O: MaybeOffset> Add<StdDuration> for DateTime<O> {
+ 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,
+ offset: self.offset,
+ }
+ }
+}
+
+impl<O: MaybeOffset> AddAssign<Duration> for DateTime<O> {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = *self + rhs;
+ }
+}
+
+impl<O: MaybeOffset> AddAssign<StdDuration> for DateTime<O> {
+ fn add_assign(&mut self, rhs: StdDuration) {
+ *self = *self + rhs;
+ }
+}
+
+impl<O: MaybeOffset> Sub<Duration> for DateTime<O> {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self {
+ self.checked_sub(duration)
+ .expect("resulting value is out of range")
+ }
+}
+
+impl<O: MaybeOffset> Sub<StdDuration> for DateTime<O> {
+ 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,
+ offset: self.offset,
+ }
+ }
+}
+
+impl<O: MaybeOffset> SubAssign<Duration> for DateTime<O> {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = *self - rhs;
+ }
+}
+
+impl<O: MaybeOffset> SubAssign<StdDuration> for DateTime<O> {
+ fn sub_assign(&mut self, rhs: StdDuration) {
+ *self = *self - rhs;
+ }
+}
+
+impl<O: MaybeOffset> Sub<Self> for DateTime<O> {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ let base = (self.date - rhs.date) + (self.time - rhs.time);
+
+ match (
+ maybe_offset_as_offset_opt::<O>(self.offset),
+ maybe_offset_as_offset_opt::<O>(rhs.offset),
+ ) {
+ (Some(self_offset), Some(rhs_offset)) => {
+ let adjustment = Duration::seconds(
+ (self_offset.whole_seconds() - rhs_offset.whole_seconds()) as i64,
+ );
+ base - adjustment
+ }
+ (left, right) => {
+ debug_assert!(
+ left.is_none() && right.is_none(),
+ "offset type should not be different for the same type"
+ );
+ base
+ }
+ }
+ }
+}
+
+#[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 {
+ (DateTime::from(self) - duration).into()
+ }
+}
+
+impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration);
+
+#[cfg(feature = "std")]
+impl Sub<SystemTime> for DateTime<offset_kind::Fixed> {
+ type Output = Duration;
+
+ fn sub(self, rhs: SystemTime) -> Self::Output {
+ self - Self::from(rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Sub<DateTime<offset_kind::Fixed>> for SystemTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: DateTime<offset_kind::Fixed>) -> Self::Output {
+ DateTime::<offset_kind::Fixed>::from(self) - rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<SystemTime> for DateTime<offset_kind::Fixed> {
+ fn eq(&self, rhs: &SystemTime) -> bool {
+ self == &Self::from(*rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<DateTime<offset_kind::Fixed>> for SystemTime {
+ fn eq(&self, rhs: &DateTime<offset_kind::Fixed>) -> bool {
+ &DateTime::<offset_kind::Fixed>::from(*self) == rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<SystemTime> for DateTime<offset_kind::Fixed> {
+ fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
+ self.partial_cmp(&Self::from(*other))
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<DateTime<offset_kind::Fixed>> for SystemTime {
+ fn partial_cmp(&self, other: &DateTime<offset_kind::Fixed>) -> Option<Ordering> {
+ DateTime::<offset_kind::Fixed>::from(*self).partial_cmp(other)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<SystemTime> for DateTime<offset_kind::Fixed> {
+ 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<DateTime<offset_kind::Fixed>> for SystemTime {
+ fn from(datetime: DateTime<offset_kind::Fixed>) -> Self {
+ let duration = datetime - DateTime::<offset_kind::Fixed>::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_family = "wasm",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+))]
+impl From<js_sys::Date> for DateTime<offset_kind::Fixed> {
+ fn from(js_date: js_sys::Date) -> Self {
+ // get_time() returns milliseconds
+ let timestamp_nanos = (js_date.get_time() * Nanosecond.per(Millisecond) as f64) as i128;
+ Self::from_unix_timestamp_nanos(timestamp_nanos)
+ .expect("invalid timestamp: Timestamp cannot fit in range")
+ }
+}
+
+#[cfg(all(
+ target_family = "wasm",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+))]
+impl From<DateTime<offset_kind::Fixed>> for js_sys::Date {
+ fn from(datetime: DateTime<offset_kind::Fixed>) -> Self {
+ // new Date() takes milliseconds
+ let timestamp =
+ (datetime.unix_timestamp_nanos() / Nanosecond.per(Millisecond) as i128) as f64;
+ js_sys::Date::new(&timestamp.into())
+ }
+}
+// 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..68a38eec78
--- /dev/null
+++ b/third_party/rust/time/src/duration.rs
@@ -0,0 +1,1418 @@
+//! 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::convert::*;
+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()
+ }
+}
+
+/// This is adapted from the `std` implementation, which uses mostly bit
+/// operations to ensure the highest precision:
+/// https://github.com/rust-lang/rust/blob/3a37c2f0523c87147b64f1b8099fc9df22e8c53e/library/core/src/time.rs#L1262-L1340
+/// Changes from `std` are marked and explained below.
+#[rustfmt::skip] // Skip `rustfmt` because it reformats the arguments of the macro weirdly.
+macro_rules! try_from_secs {
+ (
+ secs = $secs: expr,
+ mantissa_bits = $mant_bits: literal,
+ exponent_bits = $exp_bits: literal,
+ offset = $offset: literal,
+ bits_ty = $bits_ty:ty,
+ bits_ty_signed = $bits_ty_signed:ty,
+ double_ty = $double_ty:ty,
+ float_ty = $float_ty:ty,
+ is_nan = $is_nan:expr,
+ is_overflow = $is_overflow:expr,
+ ) => {{
+ 'value: {
+ const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
+ const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
+ const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
+
+ // Change from std: No error check for negative values necessary.
+
+ let bits = $secs.to_bits();
+ let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
+ let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
+
+ let (secs, nanos) = if exp < -31 {
+ // the input represents less than 1ns and can not be rounded to it
+ (0u64, 0u32)
+ } else if exp < 0 {
+ // the input is less than 1 second
+ let t = <$double_ty>::from(mant) << ($offset + exp);
+ let nanos_offset = $mant_bits + $offset;
+ let nanos_tmp = u128::from(Nanosecond.per(Second)) * u128::from(t);
+ let nanos = (nanos_tmp >> nanos_offset) as u32;
+
+ let rem_mask = (1 << nanos_offset) - 1;
+ let rem_msb_mask = 1 << (nanos_offset - 1);
+ let rem = nanos_tmp & rem_mask;
+ let is_tie = rem == rem_msb_mask;
+ let is_even = (nanos & 1) == 0;
+ let rem_msb = nanos_tmp & rem_msb_mask == 0;
+ let add_ns = !(rem_msb || (is_even && is_tie));
+
+ // f32 does not have enough precision to trigger the second branch
+ // since it can not represent numbers between 0.999_999_940_395 and 1.0.
+ let nanos = nanos + add_ns as u32;
+ if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
+ (0, nanos)
+ } else {
+ (1, 0)
+ }
+ } else if exp < $mant_bits {
+ let secs = u64::from(mant >> ($mant_bits - exp));
+ let t = <$double_ty>::from((mant << exp) & MANT_MASK);
+ let nanos_offset = $mant_bits;
+ let nanos_tmp = <$double_ty>::from(Nanosecond.per(Second)) * t;
+ let nanos = (nanos_tmp >> nanos_offset) as u32;
+
+ let rem_mask = (1 << nanos_offset) - 1;
+ let rem_msb_mask = 1 << (nanos_offset - 1);
+ let rem = nanos_tmp & rem_mask;
+ let is_tie = rem == rem_msb_mask;
+ let is_even = (nanos & 1) == 0;
+ let rem_msb = nanos_tmp & rem_msb_mask == 0;
+ let add_ns = !(rem_msb || (is_even && is_tie));
+
+ // f32 does not have enough precision to trigger the second branch.
+ // For example, it can not represent numbers between 1.999_999_880...
+ // and 2.0. Bigger values result in even smaller precision of the
+ // fractional part.
+ let nanos = nanos + add_ns as u32;
+ if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
+ (secs, nanos)
+ } else {
+ (secs + 1, 0)
+ }
+ } else if exp < 63 {
+ // Change from std: The exponent here is 63 instead of 64,
+ // because i64::MAX + 1 is 2^63.
+
+ // the input has no fractional part
+ let secs = u64::from(mant) << (exp - $mant_bits);
+ (secs, 0)
+ } else if bits == (i64::MIN as $float_ty).to_bits() {
+ // Change from std: Signed integers are asymmetrical in that
+ // iN::MIN is -iN::MAX - 1. So for example i8 covers the
+ // following numbers -128..=127. The check above (exp < 63)
+ // doesn't cover i64::MIN as that is -2^63, so we have this
+ // additional case to handle the asymmetry of iN::MIN.
+ break 'value Self::new_unchecked(i64::MIN, 0);
+ } else if $secs.is_nan() {
+ // Change from std: std doesn't differentiate between the error
+ // cases.
+ $is_nan
+ } else {
+ $is_overflow
+ };
+
+ // Change from std: All the code is mostly unmodified in that it
+ // simply calculates an unsigned integer. Here we extract the sign
+ // bit and assign it to the number. We basically manually do two's
+ // complement here, we could also use an if and just negate the
+ // numbers based on the sign, but it turns out to be quite a bit
+ // slower.
+ let mask = (bits as $bits_ty_signed) >> ($mant_bits + $exp_bits);
+ #[allow(trivial_numeric_casts)]
+ let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64);
+ #[allow(trivial_numeric_casts)]
+ let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32);
+ Self::new_unchecked(secs_signed, nanos_signed)
+ }
+ }};
+}
+
+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, -((Nanosecond.per(Second) - 1) as i32));
+
+ /// The maximum possible duration. Adding any positive duration to this will cause an overflow.
+ pub const MAX: Self = Self::new_unchecked(i64::MAX, (Nanosecond.per(Second) - 1) as _);
+ // 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 {
+ match self.seconds.checked_abs() {
+ Some(seconds) => Self::new_unchecked(seconds, self.nanoseconds.abs()),
+ None => Self::MAX,
+ }
+ }
+
+ /// 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 > -(Nanosecond.per(Second) as i32));
+ } else if seconds > 0 {
+ debug_assert!(nanoseconds >= 0);
+ debug_assert!(nanoseconds < Nanosecond.per(Second) as _);
+ } else {
+ debug_assert!(nanoseconds.unsigned_abs() < Nanosecond.per(Second));
+ }
+
+ 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 / Nanosecond.per(Second) as i64),
+ "overflow constructing `time::Duration`"
+ );
+ nanoseconds %= Nanosecond.per(Second) as i32;
+
+ if seconds > 0 && nanoseconds < 0 {
+ // `seconds` cannot overflow here because it is positive.
+ seconds -= 1;
+ nanoseconds += Nanosecond.per(Second) as i32;
+ } else if seconds < 0 && nanoseconds > 0 {
+ // `seconds` cannot overflow here because it is negative.
+ seconds += 1;
+ nanoseconds -= Nanosecond.per(Second) as i32;
+ }
+
+ 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(Second.per(Week) as _),
+ "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(Second.per(Day) as _),
+ "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(Second.per(Hour) as _),
+ "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(Second.per(Minute) as _),
+ "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 {
+ try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 52,
+ exponent_bits = 11,
+ offset = 44,
+ bits_ty = u64,
+ bits_ty_signed = i64,
+ double_ty = u128,
+ float_ty = f64,
+ is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"),
+ is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
+ )
+ }
+
+ /// 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 {
+ try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 23,
+ exponent_bits = 8,
+ offset = 41,
+ bits_ty = u32,
+ bits_ty_signed = i32,
+ double_ty = u64,
+ float_ty = f32,
+ is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"),
+ is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
+ )
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds
+ /// represented as `f64`. Any values that are out of bounds are saturated at
+ /// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
+ /// of 0 seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::saturating_seconds_f64(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::saturating_seconds_f64(-0.5), -0.5.seconds());
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f64(f64::NAN),
+ /// Duration::new(0, 0),
+ /// );
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f64(f64::NEG_INFINITY),
+ /// Duration::MIN,
+ /// );
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f64(f64::INFINITY),
+ /// Duration::MAX,
+ /// );
+ /// ```
+ pub fn saturating_seconds_f64(seconds: f64) -> Self {
+ try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 52,
+ exponent_bits = 11,
+ offset = 44,
+ bits_ty = u64,
+ bits_ty_signed = i64,
+ double_ty = u128,
+ float_ty = f64,
+ is_nan = return Self::ZERO,
+ is_overflow = return if seconds < 0.0 { Self::MIN } else { Self::MAX },
+ )
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds
+ /// represented as `f32`. Any values that are out of bounds are saturated at
+ /// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
+ /// of 0 seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::saturating_seconds_f32(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::saturating_seconds_f32(-0.5), (-0.5).seconds());
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f32(f32::NAN),
+ /// Duration::new(0, 0),
+ /// );
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f32(f32::NEG_INFINITY),
+ /// Duration::MIN,
+ /// );
+ /// assert_eq!(
+ /// Duration::saturating_seconds_f32(f32::INFINITY),
+ /// Duration::MAX,
+ /// );
+ /// ```
+ pub fn saturating_seconds_f32(seconds: f32) -> Self {
+ try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 23,
+ exponent_bits = 8,
+ offset = 41,
+ bits_ty = u32,
+ bits_ty_signed = i32,
+ double_ty = u64,
+ float_ty = f32,
+ is_nan = return Self::ZERO,
+ is_overflow = return if seconds < 0.0 { Self::MIN } else { Self::MAX },
+ )
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds
+ /// represented as `f64`. Returns `None` if the `Duration` can't be
+ /// represented.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::checked_seconds_f64(0.5), Some(0.5.seconds()));
+ /// assert_eq!(Duration::checked_seconds_f64(-0.5), Some(-0.5.seconds()));
+ /// assert_eq!(Duration::checked_seconds_f64(f64::NAN), None);
+ /// assert_eq!(Duration::checked_seconds_f64(f64::NEG_INFINITY), None);
+ /// assert_eq!(Duration::checked_seconds_f64(f64::INFINITY), None);
+ /// ```
+ pub fn checked_seconds_f64(seconds: f64) -> Option<Self> {
+ Some(try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 52,
+ exponent_bits = 11,
+ offset = 44,
+ bits_ty = u64,
+ bits_ty_signed = i64,
+ double_ty = u128,
+ float_ty = f64,
+ is_nan = return None,
+ is_overflow = return None,
+ ))
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds
+ /// represented as `f32`. Returns `None` if the `Duration` can't be
+ /// represented.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::checked_seconds_f32(0.5), Some(0.5.seconds()));
+ /// assert_eq!(Duration::checked_seconds_f32(-0.5), Some(-0.5.seconds()));
+ /// assert_eq!(Duration::checked_seconds_f32(f32::NAN), None);
+ /// assert_eq!(Duration::checked_seconds_f32(f32::NEG_INFINITY), None);
+ /// assert_eq!(Duration::checked_seconds_f32(f32::INFINITY), None);
+ /// ```
+ pub fn checked_seconds_f32(seconds: f32) -> Option<Self> {
+ Some(try_from_secs!(
+ secs = seconds,
+ mantissa_bits = 23,
+ exponent_bits = 8,
+ offset = 41,
+ bits_ty = u32,
+ bits_ty_signed = i32,
+ double_ty = u64,
+ float_ty = f32,
+ is_nan = return None,
+ is_overflow = return None,
+ ))
+ }
+
+ /// 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 / Millisecond.per(Second) as i64,
+ (milliseconds % Millisecond.per(Second) as i64 * Nanosecond.per(Millisecond) as i64)
+ as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of microseconds.
+ ///
+ /// ```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 / Microsecond.per(Second) as i64,
+ (microseconds % Microsecond.per(Second) as i64 * Nanosecond.per(Microsecond) as i64)
+ as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of nanoseconds.
+ ///
+ /// ```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 / Nanosecond.per(Second) as i64,
+ (nanoseconds % Nanosecond.per(Second) as i64) 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 / Nanosecond.per(Second) as i128;
+ let nanoseconds = nanoseconds % Nanosecond.per(Second) as i128;
+
+ if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 {
+ crate::expect_failed("overflow constructing `time::Duration`");
+ }
+
+ Self::new_unchecked(seconds as _, nanoseconds as _)
+ }
+ // 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() / Second.per(Week) as i64
+ }
+
+ /// 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() / Second.per(Day) as i64
+ }
+
+ /// 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() / Second.per(Hour) as i64
+ }
+
+ /// 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() / Second.per(Minute) as i64
+ }
+
+ /// 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 / Nanosecond.per(Second) as f64
+ }
+
+ /// 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 / Nanosecond.per(Second) as f32
+ }
+
+ /// 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 * Millisecond.per(Second) as i128
+ + self.nanoseconds as i128 / Nanosecond.per(Millisecond) as i128
+ }
+
+ /// Get the number of milliseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000..1_000`.
+ ///
+ /// ```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 / Nanosecond.per(Millisecond) as i32) 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 * Microsecond.per(Second) as i128
+ + self.nanoseconds as i128 / Nanosecond.per(Microsecond) as i128
+ }
+
+ /// Get the number of microseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000_000..1_000_000`.
+ ///
+ /// ```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 / Nanosecond.per(Microsecond) as i32
+ }
+
+ /// 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 * Nanosecond.per(Second) as i128 + 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 >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= Nanosecond.per(Second) as i32;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
+ {
+ nanoseconds += Nanosecond.per(Second) as i32;
+ 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 >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= Nanosecond.per(Second) as i32;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
+ {
+ nanoseconds += Nanosecond.per(Second) as i32;
+ 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 / Nanosecond.per(Second) as i64;
+ let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _;
+ let seconds = const_try_opt!(
+ const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs)
+ );
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// 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 * Nanosecond.per(Second) as i64).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 >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= Nanosecond.per(Second) as i32;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
+ {
+ nanoseconds += Nanosecond.per(Second) as i32;
+ 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 >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= Nanosecond.per(Second) as i32;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -(Nanosecond.per(Second) as i32) || seconds > 0 && nanoseconds < 0
+ {
+ nanoseconds += Nanosecond.per(Second) as i32;
+ 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 / Nanosecond.per(Second) as i64;
+ let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _;
+ let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _);
+ if overflow1 {
+ if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 {
+ 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 / Second.per(Day) as f64);
+ item!("h", seconds / Second.per(Hour) as f64);
+ item!("m", seconds / Second.per(Minute) as f64);
+ item!("s", seconds);
+ item!("ms", seconds * Millisecond.per(Second) as f64);
+ item!("µs", seconds * Microsecond.per(Second) as f64);
+ item!("ns", seconds * Nanosecond.per(Second) as f64);
+ } 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 / Second.per(Day) as u64)?;
+ item!(
+ "h",
+ seconds / Second.per(Hour) as u64 % Hour.per(Day) as u64
+ )?;
+ item!(
+ "m",
+ seconds / Second.per(Minute) as u64 % Minute.per(Hour) as u64
+ )?;
+ item!("s", seconds % Second.per(Minute) as u64)?;
+ item!("ms", nanoseconds / Nanosecond.per(Millisecond))?;
+ item!(
+ "µs",
+ nanoseconds / Nanosecond.per(Microsecond) as u32
+ % Microsecond.per(Millisecond) as u32
+ )?;
+ item!("ns", nanoseconds % Nanosecond.per(Microsecond) as u32)?;
+ }
+
+ 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..4b312ce2d1
--- /dev/null
+++ b/third_party/rust/time/src/error/invalid_format_description.rs
@@ -0,0 +1,128 @@
+//! Invalid format description
+
+use alloc::string::String;
+use core::fmt;
+
+use crate::error;
+
+/// The format description provided was not valid.
+#[non_exhaustive]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum InvalidFormatDescription {
+ /// There was a bracket pair that was opened but not closed.
+ #[non_exhaustive]
+ UnclosedOpeningBracket {
+ /// The zero-based index of the opening bracket.
+ index: usize,
+ },
+ /// A component name is not valid.
+ #[non_exhaustive]
+ InvalidComponentName {
+ /// The name of the invalid component name.
+ name: String,
+ /// The zero-based index the component name starts at.
+ index: usize,
+ },
+ /// A modifier is not valid.
+ #[non_exhaustive]
+ InvalidModifier {
+ /// The value of the invalid modifier.
+ value: String,
+ /// The zero-based index the modifier starts at.
+ index: usize,
+ },
+ /// A component name is missing.
+ #[non_exhaustive]
+ MissingComponentName {
+ /// The zero-based index where the component name should start.
+ index: usize,
+ },
+ /// A required modifier is missing.
+ #[non_exhaustive]
+ MissingRequiredModifier {
+ /// The name of the modifier that is missing.
+ name: &'static str,
+ /// The zero-based index of the component.
+ index: usize,
+ },
+ /// Something was expected, but not found.
+ #[non_exhaustive]
+ Expected {
+ /// What was expected to be present, but wasn't.
+ what: &'static str,
+ /// The zero-based index the item was expected to be found at.
+ index: usize,
+ },
+ /// Certain behavior is not supported in the given context.
+ #[non_exhaustive]
+ NotSupported {
+ /// The behavior that is not supported.
+ what: &'static str,
+ /// The context in which the behavior is not supported.
+ context: &'static str,
+ /// The zero-based index the error occurred at.
+ index: usize,
+ },
+}
+
+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}")
+ }
+ MissingRequiredModifier { name, index } => {
+ write!(
+ f,
+ "missing required modifier `{name}` for component at byte index {index}"
+ )
+ }
+ Expected {
+ what: expected,
+ index,
+ } => {
+ write!(f, "expected {expected} at byte index {index}")
+ }
+ NotSupported {
+ what,
+ context,
+ index,
+ } => {
+ write!(
+ f,
+ "{what} is not supported in {context} 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..8b7759d8e9
--- /dev/null
+++ b/third_party/rust/time/src/ext.rs
@@ -0,0 +1,280 @@
+//! Extension traits.
+
+use core::time::Duration as StdDuration;
+
+use crate::convert::*;
+use crate::Duration;
+
+/// Sealed trait to prevent downstream implementations.
+mod sealed {
+ /// A trait that cannot be implemented by downstream users.
+ pub trait Sealed {}
+ impl Sealed for i64 {}
+ impl Sealed for u64 {}
+ impl Sealed for f64 {}
+}
+
+// region: NumericalDuration
+/// Create [`Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`Duration`]s.
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5));
+/// assert_eq!(5.microseconds(), Duration::microseconds(5));
+/// assert_eq!(5.milliseconds(), Duration::milliseconds(5));
+/// assert_eq!(5.seconds(), Duration::seconds(5));
+/// assert_eq!(5.minutes(), Duration::minutes(5));
+/// assert_eq!(5.hours(), Duration::hours(5));
+/// assert_eq!(5.days(), Duration::days(5));
+/// assert_eq!(5.weeks(), Duration::weeks(5));
+/// ```
+///
+/// Signed integers work as well!
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5));
+/// assert_eq!((-5).microseconds(), Duration::microseconds(-5));
+/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5));
+/// assert_eq!((-5).seconds(), Duration::seconds(-5));
+/// assert_eq!((-5).minutes(), Duration::minutes(-5));
+/// assert_eq!((-5).hours(), Duration::hours(-5));
+/// assert_eq!((-5).days(), Duration::days(-5));
+/// assert_eq!((-5).weeks(), Duration::weeks(-5));
+/// ```
+///
+/// Just like any other [`Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalDuration;
+/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds());
+/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds());
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalDuration: sealed::Sealed {
+ /// Create a [`Duration`] from the number of nanoseconds.
+ fn nanoseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of microseconds.
+ fn microseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of milliseconds.
+ fn milliseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of seconds.
+ fn seconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of minutes.
+ fn minutes(self) -> Duration;
+ /// Create a [`Duration`] from the number of hours.
+ fn hours(self) -> Duration;
+ /// Create a [`Duration`] from the number of days.
+ fn days(self) -> Duration;
+ /// Create a [`Duration`] from the number of weeks.
+ fn weeks(self) -> Duration;
+}
+
+impl NumericalDuration for i64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::microseconds(self)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::milliseconds(self)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::seconds(self)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::minutes(self)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::hours(self)
+ }
+
+ fn days(self) -> Duration {
+ Duration::days(self)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::weeks(self)
+ }
+}
+
+impl NumericalDuration for f64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self as _)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Microsecond) as Self) as _)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Millisecond) as Self) as _)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Second) as Self) as _)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Minute) as Self) as _)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Hour) as Self) as _)
+ }
+
+ fn days(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Day) as Self) as _)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::nanoseconds((self * Nanosecond.per(Week) as Self) as _)
+ }
+}
+// endregion NumericalDuration
+
+// region: NumericalStdDuration
+/// Create [`std::time::Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`std::time::Duration`]s.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// # use core::time::Duration;
+/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5));
+/// assert_eq!(5.std_microseconds(), Duration::from_micros(5));
+/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5));
+/// assert_eq!(5.std_seconds(), Duration::from_secs(5));
+/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60));
+/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600));
+/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400));
+/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800));
+/// ```
+///
+/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// assert_eq!(
+/// 2.std_seconds() + 500.std_milliseconds(),
+/// 2_500.std_milliseconds()
+/// );
+/// assert_eq!(
+/// 2.std_seconds() - 500.std_milliseconds(),
+/// 1_500.std_milliseconds()
+/// );
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalStdDuration: sealed::Sealed {
+ /// Create a [`std::time::Duration`] from the number of nanoseconds.
+ fn std_nanoseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of microseconds.
+ fn std_microseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of milliseconds.
+ fn std_milliseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of seconds.
+ fn std_seconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of minutes.
+ fn std_minutes(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of hours.
+ fn std_hours(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of days.
+ fn std_days(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of weeks.
+ fn std_weeks(self) -> StdDuration;
+}
+
+impl NumericalStdDuration for u64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ StdDuration::from_nanos(self)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ StdDuration::from_micros(self)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ StdDuration::from_millis(self)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ StdDuration::from_secs(self)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ StdDuration::from_secs(self * Second.per(Minute) as Self)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ StdDuration::from_secs(self * Second.per(Hour) as Self)
+ }
+
+ fn std_days(self) -> StdDuration {
+ StdDuration::from_secs(self * Second.per(Day) as Self)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ StdDuration::from_secs(self * Second.per(Week) as Self)
+ }
+}
+
+impl NumericalStdDuration for f64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos(self as _)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Microsecond) as Self) as _)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Millisecond) as Self) as _)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Second) as Self) as _)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Minute) as Self) as _)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Hour) as Self) as _)
+ }
+
+ fn std_days(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Day) as Self) as _)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * Nanosecond.per(Week) as Self) as _)
+ }
+}
+// endregion NumericalStdDuration
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..6725590815
--- /dev/null
+++ b/third_party/rust/time/src/format_description/component.rs
@@ -0,0 +1,41 @@
+//! 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),
+ /// A number of bytes to ignore when parsing. This has no effect on formatting.
+ Ignore(modifier::Ignore),
+ /// A Unix timestamp.
+ UnixTimestamp(modifier::UnixTimestamp),
+}
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..befae8b569
--- /dev/null
+++ b/third_party/rust/time/src/format_description/mod.rs
@@ -0,0 +1,36 @@
+//! Description of how types should be formatted and parsed.
+//!
+//! The formatted value will be output to the provided writer. Format descriptions can be
+//! [well-known](crate::format_description::well_known) or obtained by using the
+//! [`format_description!`](crate::macros::format_description) macro or a function listed below.
+//!
+//! For examples, see the implementors of [Formattable](crate::formatting::Formattable),
+//! e.g. [`well_known::Rfc3339`].
+
+mod borrowed_format_item;
+mod component;
+pub 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_borrowed, 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..cdac1ae974
--- /dev/null
+++ b/third_party/rust/time/src/format_description/modifier.rs
@@ -0,0 +1,409 @@
+//! Various modifiers for components.
+
+use core::num::NonZeroU16;
+
+// 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,
+}
+
+/// Ignore some number of bytes.
+///
+/// This has no effect when formatting.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Ignore {
+ /// The number of bytes to ignore.
+ pub count: NonZeroU16,
+}
+
+// Needed as `Default` is deliberately not implemented for `Ignore`. The number of bytes to ignore
+// must be explicitly provided.
+impl Ignore {
+ /// Create an instance of `Ignore` with the provided number of bytes to ignore.
+ pub const fn count(count: NonZeroU16) -> Self {
+ Self { count }
+ }
+}
+
+/// The precision of a Unix timestamp.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum UnixTimestampPrecision {
+ /// Seconds since the Unix epoch.
+ Second,
+ /// Milliseconds since the Unix epoch.
+ Millisecond,
+ /// Microseconds since the Unix epoch.
+ Microsecond,
+ /// Nanoseconds since the Unix epoch.
+ Nanosecond,
+}
+
+/// A Unix timestamp.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct UnixTimestamp {
+ /// The precision of the timestamp.
+ pub precision: UnixTimestampPrecision,
+ /// Whether the `+` sign must be present for a non-negative timestamp.
+ pub sign_is_mandatory: bool,
+}
+
+/// 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;
+ /// Creates a modifier that indicates the value represents the [number of seconds](Self::Second)
+ /// since the Unix epoch.
+ UnixTimestampPrecision => Self::Second;
+ /// Creates a modifier that indicates the value represents the [number of
+ /// seconds](UnixTimestampPrecision::Second) since the Unix epoch. The sign is not mandatory.
+ @pub UnixTimestamp => Self {
+ precision: UnixTimestampPrecision::Second,
+ sign_is_mandatory: false,
+ };
+}
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..4957fe0968
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/ast.rs
@@ -0,0 +1,383 @@
+//! AST for parsing format descriptions.
+
+use alloc::boxed::Box;
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::iter;
+
+use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused};
+
+/// One part of a complete format description.
+pub(super) enum Item<'a> {
+ /// A literal string, formatted and parsed as-is.
+ ///
+ /// This should never be present inside a nested format description.
+ Literal(Spanned<&'a [u8]>),
+ /// A sequence of brackets. The first acts as the escape character.
+ ///
+ /// This should never be present if the lexer has `BACKSLASH_ESCAPE` set to `true`.
+ EscapedBracket {
+ /// The first bracket.
+ _first: Unused<Location>,
+ /// The second bracket.
+ _second: Unused<Location>,
+ },
+ /// Part of a type, along with its modifiers.
+ Component {
+ /// Where the opening bracket was in the format string.
+ _opening_bracket: Unused<Location>,
+ /// Whitespace between the opening bracket and name.
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The name of the component.
+ name: Spanned<&'a [u8]>,
+ /// The modifiers for the component.
+ modifiers: Box<[Modifier<'a>]>,
+ /// Whitespace between the modifiers and closing bracket.
+ _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// Where the closing bracket was in the format string.
+ _closing_bracket: Unused<Location>,
+ },
+ /// An optional sequence of items.
+ Optional {
+ /// Where the opening bracket was in the format string.
+ opening_bracket: Location,
+ /// Whitespace between the opening bracket and "optional".
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The "optional" keyword.
+ _optional_kw: Unused<Spanned<&'a [u8]>>,
+ /// Whitespace between the "optional" keyword and the opening bracket.
+ _whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The items within the optional sequence.
+ nested_format_description: NestedFormatDescription<'a>,
+ /// Where the closing bracket was in the format string.
+ closing_bracket: Location,
+ },
+ /// The first matching parse of a sequence of items.
+ First {
+ /// Where the opening bracket was in the format string.
+ opening_bracket: Location,
+ /// Whitespace between the opening bracket and "first".
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The "first" keyword.
+ _first_kw: Unused<Spanned<&'a [u8]>>,
+ /// Whitespace between the "first" keyword and the opening bracket.
+ _whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The sequences of items to try.
+ nested_format_descriptions: Box<[NestedFormatDescription<'a>]>,
+ /// Where the closing bracket was in the format string.
+ closing_bracket: Location,
+ },
+}
+
+/// A format description that is nested within another format description.
+pub(super) struct NestedFormatDescription<'a> {
+ /// Where the opening bracket was in the format string.
+ pub(super) _opening_bracket: Unused<Location>,
+ /// The items within the nested format description.
+ pub(super) items: Box<[Item<'a>]>,
+ /// Where the closing bracket was in the format string.
+ pub(super) _closing_bracket: Unused<Location>,
+ /// Whitespace between the closing bracket and the next item.
+ pub(super) _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+}
+
+/// A modifier for a component.
+pub(super) struct Modifier<'a> {
+ /// Whitespace preceding the modifier.
+ pub(super) _leading_whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The key of the modifier.
+ pub(super) key: Spanned<&'a [u8]>,
+ /// Where the colon of the modifier was in the format string.
+ pub(super) _colon: Unused<Location>,
+ /// The value of the modifier.
+ pub(super) value: Spanned<&'a [u8]>,
+}
+
+/// Parse the provided tokens into an AST.
+pub(super) fn parse<
+ 'item: 'iter,
+ 'iter,
+ I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
+ const VERSION: usize,
+>(
+ tokens: &'iter mut lexer::Lexed<I>,
+) -> impl Iterator<Item = Result<Item<'item>, Error>> + 'iter {
+ validate_version!(VERSION);
+ parse_inner::<_, false, VERSION>(tokens)
+}
+
+/// Parse the provided tokens into an AST. The const generic indicates whether the resulting
+/// [`Item`] will be used directly or as part of a [`NestedFormatDescription`].
+fn parse_inner<
+ 'item,
+ I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
+ const NESTED: bool,
+ const VERSION: usize,
+>(
+ tokens: &mut lexer::Lexed<I>,
+) -> impl Iterator<Item = Result<Item<'item>, Error>> + '_ {
+ validate_version!(VERSION);
+ iter::from_fn(move || {
+ if NESTED && tokens.peek_closing_bracket().is_some() {
+ return None;
+ }
+
+ let next = match tokens.next()? {
+ Ok(token) => token,
+ Err(err) => return Some(Err(err)),
+ };
+
+ Some(match next {
+ lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => {
+ bug!("literal should not be present in nested description")
+ }
+ lexer::Token::Literal(value) => Ok(Item::Literal(value)),
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Opening,
+ location,
+ } => {
+ if version!(..=1) {
+ if let Some(second_location) = tokens.next_if_opening_bracket() {
+ Ok(Item::EscapedBracket {
+ _first: unused(location),
+ _second: unused(second_location),
+ })
+ } else {
+ parse_component::<_, VERSION>(location, tokens)
+ }
+ } else {
+ parse_component::<_, VERSION>(location, tokens)
+ }
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } if NESTED => {
+ bug!("closing bracket should be caught by the `if` statement")
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } => {
+ bug!("closing bracket should have been consumed by `parse_component`")
+ }
+ lexer::Token::ComponentPart {
+ kind: _, // whitespace is significant in nested components
+ value,
+ } if NESTED => Ok(Item::Literal(value)),
+ lexer::Token::ComponentPart { kind: _, value: _ } => {
+ bug!("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,
+ I: Iterator<Item = Result<lexer::Token<'a>, Error>>,
+ const VERSION: usize,
+>(
+ opening_bracket: Location,
+ tokens: &mut lexer::Lexed<I>,
+) -> Result<Item<'a>, Error> {
+ validate_version!(VERSION);
+ let leading_whitespace = tokens.next_if_whitespace();
+
+ let Some(name) = tokens.next_if_not_whitespace() else {
+ let span = match leading_whitespace {
+ Some(Spanned { value: _, span }) => span,
+ None => opening_bracket.to(opening_bracket),
+ };
+ return Err(Error {
+ _inner: unused(span.error("expected component name")),
+ public: crate::error::InvalidFormatDescription::MissingComponentName {
+ index: span.start.byte as _,
+ },
+ });
+ };
+
+ if *name == b"optional" {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ return Err(Error {
+ _inner: unused(name.span.error("expected whitespace after `optional`")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "whitespace after `optional`",
+ index: name.span.end.byte as _,
+ },
+ });
+ };
+
+ let nested = parse_nested::<_, VERSION>(whitespace.span.end, tokens)?;
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as _,
+ },
+ });
+ };
+
+ return Ok(Item::Optional {
+ opening_bracket,
+ _leading_whitespace: unused(leading_whitespace),
+ _optional_kw: unused(name),
+ _whitespace: unused(whitespace),
+ nested_format_description: nested,
+ closing_bracket,
+ });
+ }
+
+ if *name == b"first" {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ return Err(Error {
+ _inner: unused(name.span.error("expected whitespace after `first`")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "whitespace after `first`",
+ index: name.span.end.byte as _,
+ },
+ });
+ };
+
+ let mut nested_format_descriptions = Vec::new();
+ while let Ok(description) = parse_nested::<_, VERSION>(whitespace.span.end, tokens) {
+ nested_format_descriptions.push(description);
+ }
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as _,
+ },
+ });
+ };
+
+ return Ok(Item::First {
+ opening_bracket,
+ _leading_whitespace: unused(leading_whitespace),
+ _first_kw: unused(name),
+ _whitespace: unused(whitespace),
+ nested_format_descriptions: nested_format_descriptions.into_boxed_slice(),
+ closing_bracket,
+ });
+ }
+
+ let mut modifiers = Vec::new();
+ let trailing_whitespace = loop {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ break None;
+ };
+
+ // This is not necessary for proper parsing, but provides a much better error when a nested
+ // description is used where it's not allowed.
+ if let Some(location) = tokens.next_if_opening_bracket() {
+ return Err(Error {
+ _inner: unused(
+ location
+ .to(location)
+ .error("modifier must be of the form `key:value`"),
+ ),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from("["),
+ index: location.byte as _,
+ },
+ });
+ }
+
+ let Some(Spanned { value, span }) = tokens.next_if_not_whitespace() else {
+ break Some(whitespace);
+ };
+
+ let Some(colon_index) = value.iter().position(|&b| b == b':') else {
+ return Err(Error {
+ _inner: unused(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 as _,
+ },
+ });
+ };
+ let key = &value[..colon_index];
+ let value = &value[colon_index + 1..];
+
+ if key.is_empty() {
+ return Err(Error {
+ _inner: unused(span.shrink_to_start().error("expected modifier key")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.start.byte as _,
+ },
+ });
+ }
+ if value.is_empty() {
+ return Err(Error {
+ _inner: unused(span.shrink_to_end().error("expected modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.shrink_to_end().start.byte as _,
+ },
+ });
+ }
+
+ modifiers.push(Modifier {
+ _leading_whitespace: unused(whitespace),
+ key: key.spanned(span.shrink_to_before(colon_index as _)),
+ _colon: unused(span.start.offset(colon_index as _)),
+ value: value.spanned(span.shrink_to_after(colon_index as _)),
+ });
+ };
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as _,
+ },
+ });
+ };
+
+ Ok(Item::Component {
+ _opening_bracket: unused(opening_bracket),
+ _leading_whitespace: unused(leading_whitespace),
+ name,
+ modifiers: modifiers.into_boxed_slice(),
+ _trailing_whitespace: unused(trailing_whitespace),
+ _closing_bracket: unused(closing_bracket),
+ })
+}
+
+/// Parse a nested format description. The location provided is the the most recent one consumed.
+fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: usize>(
+ last_location: Location,
+ tokens: &mut lexer::Lexed<I>,
+) -> Result<NestedFormatDescription<'a>, Error> {
+ validate_version!(VERSION);
+ let Some(opening_bracket) = tokens.next_if_opening_bracket() else {
+ return Err(Error {
+ _inner: unused(last_location.error("expected opening bracket")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "opening bracket",
+ index: last_location.byte as _,
+ },
+ });
+ };
+ let items = parse_inner::<_, true, VERSION>(tokens).collect::<Result<_, _>>()?;
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as _,
+ },
+ });
+ };
+ let trailing_whitespace = tokens.next_if_whitespace();
+
+ Ok(NestedFormatDescription {
+ _opening_bracket: unused(opening_bracket),
+ items,
+ _closing_bracket: unused(closing_bracket),
+ _trailing_whitespace: unused(trailing_whitespace),
+ })
+}
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..c0e64d92a2
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/format_item.rs
@@ -0,0 +1,542 @@
+//! Typed, validated representation of a parsed format description.
+
+use alloc::boxed::Box;
+use alloc::string::String;
+use core::num::NonZeroU16;
+use core::str::{self, FromStr};
+
+use super::{ast, unused, Error, Span, Spanned};
+
+/// 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.
+pub(super) enum Item<'a> {
+ /// A literal string.
+ Literal(&'a [u8]),
+ /// Part of a type, along with its modifiers.
+ Component(Component),
+ /// A sequence of optional items.
+ Optional {
+ /// The items themselves.
+ value: Box<[Self]>,
+ /// The span of the full sequence.
+ span: Span,
+ },
+ /// The first matching parse of a sequence of format descriptions.
+ First {
+ /// The sequence of format descriptions.
+ value: Box<[Box<[Self]>]>,
+ /// The span of the full sequence.
+ span: Span,
+ },
+}
+
+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(Spanned { value, span: _ }) => Item::Literal(value),
+ ast::Item::EscapedBracket {
+ _first: _,
+ _second: _,
+ } => Item::Literal(b"["),
+ ast::Item::Optional {
+ opening_bracket,
+ _leading_whitespace: _,
+ _optional_kw: _,
+ _whitespace: _,
+ nested_format_description,
+ closing_bracket,
+ } => {
+ let items = nested_format_description
+ .items
+ .into_vec()
+ .into_iter()
+ .map(Item::from_ast)
+ .collect::<Result<_, _>>()?;
+ Item::Optional {
+ value: items,
+ span: opening_bracket.to(closing_bracket),
+ }
+ }
+ ast::Item::First {
+ opening_bracket,
+ _leading_whitespace: _,
+ _first_kw: _,
+ _whitespace: _,
+ nested_format_descriptions,
+ closing_bracket,
+ } => {
+ let items = nested_format_descriptions
+ .into_vec()
+ .into_iter()
+ .map(|nested_format_description| {
+ nested_format_description
+ .items
+ .into_vec()
+ .into_iter()
+ .map(Item::from_ast)
+ .collect()
+ })
+ .collect::<Result<_, _>>()?;
+ Item::First {
+ value: items,
+ span: opening_bracket.to(closing_bracket),
+ }
+ }
+ })
+ }
+}
+
+impl<'a> TryFrom<Item<'a>> for crate::format_description::FormatItem<'a> {
+ type Error = Error;
+
+ fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
+ match item {
+ Item::Literal(literal) => Ok(Self::Literal(literal)),
+ Item::Component(component) => Ok(Self::Component(component.into())),
+ Item::Optional { value: _, span } => Err(Error {
+ _inner: unused(span.error(
+ "optional items are not supported in runtime-parsed format descriptions",
+ )),
+ public: crate::error::InvalidFormatDescription::NotSupported {
+ what: "optional item",
+ context: "runtime-parsed format descriptions",
+ index: span.start.byte as _,
+ },
+ }),
+ Item::First { value: _, span } => Err(Error {
+ _inner: unused(span.error(
+ "'first' items are not supported in runtime-parsed format descriptions",
+ )),
+ public: crate::error::InvalidFormatDescription::NotSupported {
+ what: "'first' item",
+ context: "runtime-parsed format descriptions",
+ index: span.start.byte as _,
+ },
+ }),
+ }
+ }
+}
+
+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()),
+ Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())),
+ Item::First { value, span: _ } => {
+ Self::First(value.into_vec().into_iter().map(Into::into).collect())
+ }
+ }
+ }
+}
+
+impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
+ fn from(items: Box<[Item<'a>]>) -> Self {
+ let items = items.into_vec();
+ if items.len() == 1 {
+ if let Ok([item]) = <[_; 1]>::try_from(items) {
+ item.into()
+ } else {
+ bug!("the length was just checked to be 1")
+ }
+ } else {
+ Self::Compound(items.into_iter().map(Self::from).collect())
+ }
+ }
+}
+
+/// Declare the `Component` struct.
+macro_rules! component_definition {
+ (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
+ (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
+ (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
+ (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
+
+ ($vis:vis enum $name:ident {
+ $($variant:ident = $parse_variant:literal {$(
+ $(#[$required:tt])?
+ $field:ident = $parse_field:literal:
+ Option<$(#[$from_str:tt])? $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<'_>],
+ _component_span: Span,
+ ) -> Result<Self, Error>
+ {
+ let mut this = Self {
+ $($field: None),*
+ };
+
+ for modifier in modifiers {
+ $(#[allow(clippy::string_lit_as_bytes)]
+ if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
+ this.$field = component_definition!(@if_from_str $($from_str)?
+ then {
+ parse_from_modifier_value::<$field_type>(&modifier.value)?
+ } else {
+ <$field_type>::from_modifier_value(&modifier.value)?
+ });
+ continue;
+ })*
+ return Err(Error {
+ _inner: unused(modifier.key.span.error("invalid modifier key")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(*modifier.key).into_owned(),
+ index: modifier.key.span.start.byte as _,
+ }
+ });
+ }
+
+ $(component_definition! { @if_required $($required)? then {
+ if this.$field.is_none() {
+ return Err(Error {
+ _inner: unused(_component_span.error("missing required modifier")),
+ public:
+ crate::error::InvalidFormatDescription::MissingRequiredModifier {
+ name: $parse_field,
+ index: _component_span.start.byte as _,
+ }
+ });
+ }
+ }})*
+
+ 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: component_definition! { @if_required $($required)?
+ then {
+ match $field {
+ Some(value) => value.into(),
+ None => bug!("required modifier was not set"),
+ }
+ } else {
+ $field.unwrap_or_default().into()
+ }
+ }
+ ),*}
+ )
+ }
+ )*}
+ }
+ }
+
+ /// Parse a component from the AST, given its name and modifiers.
+ fn component_from_ast(
+ name: &Spanned<&[u8]>,
+ modifiers: &[ast::Modifier<'_>],
+ ) -> Result<Component, Error> {
+ $(#[allow(clippy::string_lit_as_bytes)]
+ if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
+ return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?,));
+ })*
+ Err(Error {
+ _inner: unused(name.span.error("invalid component")),
+ public: crate::error::InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(name).into_owned(),
+ index: name.span.start.byte as _,
+ },
+ })
+ }
+ }
+}
+
+// Keep in alphabetical order.
+component_definition! {
+ pub(super) enum Component {
+ Day = "day" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Hour = "hour" {
+ padding = "padding": Option<Padding> => padding,
+ base = "repr": Option<HourBase> => is_12_hour_clock,
+ },
+ Ignore = "ignore" {
+ #[required]
+ count = "count": Option<#[from_str] NonZeroU16> => count,
+ },
+ Minute = "minute" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Month = "month" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<MonthRepr> => repr,
+ case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
+ },
+ OffsetHour = "offset_hour" {
+ sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
+ padding = "padding": Option<Padding> => padding,
+ },
+ OffsetMinute = "offset_minute" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ OffsetSecond = "offset_second" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Ordinal = "ordinal" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Period = "period" {
+ case = "case": Option<PeriodCase> => is_uppercase,
+ case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
+ },
+ Second = "second" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Subsecond = "subsecond" {
+ digits = "digits": Option<SubsecondDigits> => digits,
+ },
+ UnixTimestamp = "unix_timestamp" {
+ precision = "precision": Option<UnixTimestampPrecision> => precision,
+ sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
+ },
+ Weekday = "weekday" {
+ repr = "repr": Option<WeekdayRepr> => repr,
+ one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
+ case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
+ },
+ WeekNumber = "week_number" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<WeekNumberRepr> => repr,
+ },
+ Year = "year" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<YearRepr> => repr,
+ base = "base": Option<YearBase> => iso_week_based,
+ sign_behavior = "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
+ };
+}
+
+/// 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
+ ),* $(,)?
+ }
+ )+) => {$(
+ #[derive(Default)]
+ enum $name {
+ $($(#[$attr])? $variant),*
+ }
+
+ impl $name {
+ /// Parse the modifier from its string representation.
+ fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
+ $(if value.eq_ignore_ascii_case($parse_variant) {
+ return Ok(Some(Self::$variant));
+ })*
+ Err(Error {
+ _inner: unused(value.span.error("invalid modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: value.span.start.byte as _,
+ },
+ })
+ }
+ }
+
+ 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 UnixTimestampPrecision {
+ #[default]
+ Second = b"second",
+ Millisecond = b"millisecond",
+ Microsecond = b"microsecond",
+ Nanosecond = b"nanosecond",
+ }
+
+ 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",
+ }
+}
+
+/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
+fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
+ str::from_utf8(value)
+ .ok()
+ .and_then(|val| val.parse::<T>().ok())
+ .map(|val| Some(val))
+ .ok_or_else(|| Error {
+ _inner: unused(value.span.error("invalid modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: value.span.start.byte as _,
+ },
+ })
+}
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..1604fd4971
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/lexer.rs
@@ -0,0 +1,299 @@
+//! Lexer for parsing format descriptions.
+
+use core::iter;
+
+use super::{unused, Error, Location, Spanned, SpannedValue};
+
+/// An iterator over the lexed tokens.
+pub(super) struct Lexed<I: Iterator> {
+ /// The internal iterator.
+ iter: core::iter::Peekable<I>,
+}
+
+impl<I: Iterator> Iterator for Lexed<I> {
+ type Item = I::Item;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next()
+ }
+}
+
+impl<'iter, 'token: 'iter, I: Iterator<Item = Result<Token<'token>, Error>> + 'iter> Lexed<I> {
+ /// Peek at the next item in the iterator.
+ pub(super) fn peek(&mut self) -> Option<&I::Item> {
+ self.iter.peek()
+ }
+
+ /// Consume the next token if it is whitespace.
+ pub(super) fn next_if_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> {
+ if let Some(&Ok(Token::ComponentPart {
+ kind: ComponentKind::Whitespace,
+ value,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is a component item that is not whitespace.
+ pub(super) fn next_if_not_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> {
+ if let Some(&Ok(Token::ComponentPart {
+ kind: ComponentKind::NotWhitespace,
+ value,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is an opening bracket.
+ pub(super) fn next_if_opening_bracket(&mut self) -> Option<Location> {
+ if let Some(&Ok(Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(location)
+ } else {
+ None
+ }
+ }
+
+ /// Peek at the next token if it is a closing bracket.
+ pub(super) fn peek_closing_bracket(&'iter mut self) -> Option<&'iter Location> {
+ if let Some(Ok(Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ })) = self.peek()
+ {
+ Some(location)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is a closing bracket.
+ pub(super) fn next_if_closing_bracket(&mut self) -> Option<Location> {
+ if let Some(&Ok(Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(location)
+ } else {
+ None
+ }
+ }
+}
+
+/// 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(Spanned<&'a [u8]>),
+ /// 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: Spanned<&'a [u8]>,
+ },
+}
+
+/// 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<'item>(
+ iter: impl Iterator<Item = &'item u8>,
+) -> impl Iterator<Item = (&'item u8, Location)> {
+ let mut byte_pos = 0;
+
+ iter.map(move |byte| {
+ let location = Location { byte: byte_pos };
+ byte_pos += 1;
+ (byte, location)
+ })
+}
+
+/// Parse the string into a series of [`Token`]s.
+///
+/// `VERSION` controls the version of the format description that is being parsed. Currently, this
+/// must be 1 or 2.
+///
+/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`.
+/// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may
+/// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All
+/// other characters result in a lex error.
+pub(super) fn lex<const VERSION: usize>(
+ mut input: &[u8],
+) -> Lexed<impl Iterator<Item = Result<Token<'_>, Error>>> {
+ validate_version!(VERSION);
+
+ let mut depth: u8 = 0;
+ let mut iter = attach_location(input.iter()).peekable();
+ let mut second_bracket_location = None;
+
+ let iter = iter::from_fn(move || {
+ // The flag is only set when version is zero.
+ if version!(..=1) {
+ // 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(Ok(Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }));
+ }
+ }
+
+ Some(Ok(match iter.next()? {
+ // possible escape sequence
+ (b'\\', backslash_loc) if version!(2..) => {
+ match iter.next() {
+ Some((b'\\' | b'[' | b']', char_loc)) => {
+ // The escaped character is emitted as-is.
+ let char = &input[1..2];
+ input = &input[2..];
+ if depth == 0 {
+ Token::Literal(char.spanned(backslash_loc.to(char_loc)))
+ } else {
+ Token::ComponentPart {
+ kind: ComponentKind::NotWhitespace,
+ value: char.spanned(backslash_loc.to(char_loc)),
+ }
+ }
+ }
+ Some((_, loc)) => {
+ return Some(Err(Error {
+ _inner: unused(loc.error("invalid escape sequence")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "valid escape sequence",
+ index: loc.byte as _,
+ },
+ }));
+ }
+ None => {
+ return Some(Err(Error {
+ _inner: unused(backslash_loc.error("unexpected end of input")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "valid escape sequence",
+ index: backslash_loc.byte as _,
+ },
+ }));
+ }
+ }
+ }
+ // potentially escaped opening bracket
+ (b'[', location) if version!(..=1) => {
+ if let Some((_, second_location)) = iter.next_if(|&(&byte, _)| byte == b'[') {
+ // Escaped bracket. Store the location of the second so we can emit it later.
+ second_bracket_location = Some(second_location);
+ input = &input[2..];
+ } else {
+ // opening bracket
+ depth += 1;
+ input = &input[1..];
+ }
+
+ Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }
+ }
+ // opening bracket
+ (b'[', location) => {
+ 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, _)| !((version!(2..) && byte == b'\\') || byte == b'['))
+ {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+
+ Token::Literal(value.spanned(start_location.to(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, _)| {
+ !matches!(byte, b'\\' | b'[' | 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: value.spanned(start_location.to(end_location)),
+ }
+ }
+ }))
+ });
+
+ Lexed {
+ iter: iter.peekable(),
+ }
+}
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..2ab58f172a
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/mod.rs
@@ -0,0 +1,245 @@
+//! Parser for format descriptions.
+
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+
+/// A helper macro to make version restrictions simpler to read and write.
+macro_rules! version {
+ ($range:expr) => {
+ $range.contains(&VERSION)
+ };
+}
+
+/// A helper macro to statically validate the version (when used as a const parameter).
+macro_rules! validate_version {
+ ($version:ident) => {
+ #[allow(clippy::let_unit_value)]
+ let _ = $crate::format_description::parse::Version::<$version>::IS_VALID;
+ };
+}
+
+mod ast;
+mod format_item;
+mod lexer;
+
+/// A struct that is used to ensure that the version is valid.
+struct Version<const N: usize>;
+impl<const N: usize> Version<N> {
+ /// A constant that panics if the version is not valid. This results in a post-monomorphization
+ /// error.
+ const IS_VALID: () = assert!(N >= 1 && N <= 2);
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html).
+///
+/// This function exists for backward compatibility reasons. It is equivalent to calling
+/// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of
+/// `parse_borrowed`.
+pub fn parse(
+ s: &str,
+) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
+{
+ parse_borrowed::<1>(s)
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
+/// description is provided as the const parameter. **It is recommended to use version 2.**
+pub fn parse_borrowed<const VERSION: usize>(
+ s: &str,
+) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
+{
+ validate_version!(VERSION);
+ let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
+ let ast = ast::parse::<_, VERSION>(&mut lexed);
+ let format_items = format_item::parse(ast);
+ Ok(format_items
+ .map(|res| res.and_then(TryInto::try_into))
+ .collect::<Result<_, _>>()?)
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
+/// description is provided as the const parameter.
+///
+/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
+/// that there is no lifetime that needs to be handled. **It is recommended to use version 2.**
+///
+/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
+pub fn parse_owned<const VERSION: usize>(
+ s: &str,
+) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> {
+ validate_version!(VERSION);
+ let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
+ let ast = ast::parse::<_, VERSION>(&mut lexed);
+ let format_items = format_item::parse(ast);
+ let items = format_items
+ .map(|res| res.map(Into::into))
+ .collect::<Result<Box<_>, _>>()?;
+ Ok(items.into())
+}
+
+/// A location within a string.
+#[derive(Clone, Copy)]
+struct Location {
+ /// The zero-indexed byte of the string.
+ byte: u32,
+}
+
+impl Location {
+ /// Create a new [`Span`] from `self` to `other`.
+ const fn to(self, end: Self) -> Span {
+ Span { start: self, end }
+ }
+
+ /// 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: u32) -> Self {
+ Self {
+ 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 {
+ /// 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,
+ }
+ }
+
+ /// Obtain a `Span` that ends before the provided position of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_before(&self, pos: u32) -> Self {
+ Self {
+ start: self.start,
+ end: Location {
+ byte: self.start.byte + pos - 1,
+ },
+ }
+ }
+
+ /// Obtain a `Span` that starts after provided position to the end of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_after(&self, pos: u32) -> Self {
+ Self {
+ start: Location {
+ byte: self.start.byte + pos + 1,
+ },
+ 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,
+ }
+ }
+}
+
+/// A value with an associated [`Span`].
+#[derive(Clone, Copy)]
+struct Spanned<T> {
+ /// The value.
+ value: T,
+ /// Where the value was in the format string.
+ span: Span,
+}
+
+impl<T> core::ops::Deref for Spanned<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+/// Helper trait to attach a [`Span`] to a value.
+trait SpannedValue: Sized {
+ /// Attach a [`Span`] to a value.
+ fn spanned(self, span: Span) -> Spanned<Self>;
+}
+
+impl<T> SpannedValue for T {
+ fn spanned(self, span: Span) -> Spanned<Self> {
+ Spanned { value: self, span }
+ }
+}
+
+/// The internal error type.
+struct ErrorInner {
+ /// The message displayed to the user.
+ _message: &'static str,
+ /// Where the error originated.
+ _span: Span,
+}
+
+/// A complete error description.
+struct Error {
+ /// The internal error.
+ _inner: Unused<ErrorInner>,
+ /// The error needed for interoperability with the rest of `time`.
+ public: crate::error::InvalidFormatDescription,
+}
+
+impl From<Error> for crate::error::InvalidFormatDescription {
+ fn from(error: Error) -> Self {
+ error.public
+ }
+}
+
+/// A value that may be used in the future, but currently is not.
+///
+/// This struct exists so that data can semantically be passed around without _actually_ passing it
+/// around. This way the data still exists if it is needed in the future.
+// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
+struct Unused<T>(core::marker::PhantomData<T>);
+
+/// Indicate that a value is currently unused.
+#[allow(clippy::missing_const_for_fn)] // false positive
+fn unused<T>(_: T) -> Unused<T> {
+ Unused(core::marker::PhantomData)
+}
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..1fdecaf269
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
@@ -0,0 +1,250 @@
+//! 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`](super::Iso8601).
+///
+/// The type this is aliased to must not be relied upon. It can change in any release without
+/// notice.
+pub type EncodedConfig = DoNotRelyOnWhatThisIs;
+
+#[cfg(feature = "formatting")]
+impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
+ /// The user-provided configuration for the ISO 8601 format.
+ const CONFIG: Config = Config::decode(CONFIG);
+ /// Whether the date should be formatted.
+ pub(crate) const FORMAT_DATE: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Date | FC::DateTime | FC::DateTimeOffset
+ );
+ /// Whether the time should be formatted.
+ pub(crate) const FORMAT_TIME: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset
+ );
+ /// Whether the UTC offset should be formatted.
+ pub(crate) const FORMAT_OFFSET: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Offset | FC::DateTimeOffset | FC::TimeOffset
+ );
+ /// Whether the year is six digits.
+ pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits;
+ /// Whether the format contains separators (such as `-` or `:`).
+ pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators;
+ /// Which format to use for the date.
+ pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind;
+ /// The precision and number of decimal digits to use for the time.
+ pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision;
+ /// The precision for the UTC offset.
+ pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision;
+}
+
+impl Config {
+ /// Encode the configuration, permitting it to be used as a const parameter of
+ /// [`Iso8601`](super::Iso8601).
+ ///
+ /// The value returned by this method must only be used as a const parameter to
+ /// [`Iso8601`](super::Iso8601). Any other usage is unspecified behavior.
+ 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..2b4b350883
--- /dev/null
+++ b/third_party/rust/time/src/formatting/formattable.rs
@@ -0,0 +1,307 @@
+//! 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 describes a format.
+///
+/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
+///
+/// [`Date::format`] and [`Time::format`] each use a format description to generate
+/// a String from their data. See the respective methods for usage examples.
+#[cfg_attr(__time_03_docs, doc(notable_trait))]
+pub trait Formattable: sealed::Sealed {}
+impl Formattable for FormatItem<'_> {}
+impl Formattable for [FormatItem<'_>] {}
+impl Formattable for 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..29d443ef46
--- /dev/null
+++ b/third_party/rust/time/src/formatting/iso8601.rs
@@ -0,0 +1,140 @@
+//! Helpers for implementing formatting for ISO 8601.
+
+use std::io;
+
+use crate::convert::*;
+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<const CONFIG: EncodedConfig>(
+ output: &mut impl io::Write,
+ 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<const CONFIG: EncodedConfig>(
+ output: &mut impl io::Write,
+ 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) / Minute.per(Hour) as f64
+ + (seconds as f64) / Second.per(Hour) as f64
+ + (nanoseconds as f64) / Nanosecond.per(Hour) as f64;
+ format_float(output, hours, 2, decimal_digits)?;
+ }
+ TimePrecision::Minute { decimal_digits } => {
+ bytes += format_number_pad_zero::<2>(output, hours)?;
+ bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
+ let minutes = (minutes as f64)
+ + (seconds as f64) / Second.per(Minute) as f64
+ + (nanoseconds as f64) / Nanosecond.per(Minute) as f64;
+ 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) / Nanosecond.per(Second) as f64;
+ bytes += format_float(output, seconds, 2, decimal_digits)?;
+ }
+ }
+
+ Ok(bytes)
+}
+
+/// Format the UTC offset portion of ISO 8601.
+pub(super) fn format_offset<const CONFIG: EncodedConfig>(
+ output: &mut impl io::Write,
+ 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..e5017063ab
--- /dev/null
+++ b/third_party/rust/time/src/formatting/mod.rs
@@ -0,0 +1,545 @@
+//! Formatting for various types.
+
+pub(crate) mod formattable;
+mod iso8601;
+
+use core::num::NonZeroU8;
+use std::io;
+
+pub use self::formattable::Formattable;
+use crate::convert::*;
+use crate::format_description::{modifier, Component};
+use crate::{error, Date, OffsetDateTime, 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$}")?;
+ Ok(width)
+ }
+ None => {
+ let value = value.trunc() as u64;
+ let width = digits_before_decimal as usize;
+ write!(output, "{value:0>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>(
+ output: &mut impl io::Write,
+ value: impl itoa::Integer + DigitCount + Copy,
+ 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 => format_number_pad_none(output, value),
+ }
+}
+
+/// 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>(
+ output: &mut impl io::Write,
+ value: impl itoa::Integer + DigitCount + Copy,
+) -> 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>(
+ output: &mut impl io::Write,
+ value: impl itoa::Integer + DigitCount + Copy,
+) -> 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 a number with no padding.
+///
+/// If the sign is mandatory, the sign must be written by the caller.
+pub(crate) fn format_number_pad_none(
+ output: &mut impl io::Write,
+ value: impl itoa::Integer + Copy,
+) -> Result<usize, io::Error> {
+ write(output, itoa::Buffer::new().format(value).as_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)?,
+ (Ignore(_), ..) => 0,
+ (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => {
+ fmt_unix_timestamp(output, date, time, 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
+
+/// Format the Unix timestamp into the designated output.
+fn fmt_unix_timestamp(
+ output: &mut impl io::Write,
+ date: Date,
+ time: Time,
+ offset: UtcOffset,
+ modifier::UnixTimestamp {
+ precision,
+ sign_is_mandatory,
+ }: modifier::UnixTimestamp,
+) -> Result<usize, io::Error> {
+ let date_time = date
+ .with_time(time)
+ .assume_offset(offset)
+ .to_offset(UtcOffset::UTC);
+
+ if date_time < OffsetDateTime::UNIX_EPOCH {
+ write(output, b"-")?;
+ } else if sign_is_mandatory {
+ write(output, b"+")?;
+ }
+
+ match precision {
+ modifier::UnixTimestampPrecision::Second => {
+ format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs())
+ }
+ modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none(
+ output,
+ (date_time.unix_timestamp_nanos() / Nanosecond.per(Millisecond) as i128).unsigned_abs(),
+ ),
+ modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none(
+ output,
+ (date_time.unix_timestamp_nanos() / Nanosecond.per(Microsecond) as i128).unsigned_abs(),
+ ),
+ modifier::UnixTimestampPrecision::Nanosecond => {
+ format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs())
+ }
+ }
+}
diff --git a/third_party/rust/time/src/instant.rs b/third_party/rust/time/src/instant.rs
new file mode 100644
index 0000000000..58579b101c
--- /dev/null
+++ b/third_party/rust/time/src/instant.rs
@@ -0,0 +1,277 @@
+//! 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;
+
+ /// # Panics
+ ///
+ /// This function may panic if the resulting point in time cannot be represented by the
+ /// underlying data structure.
+ fn add(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ Self(self.0 + duration.unsigned_abs())
+ } else if duration.is_negative() {
+ #[allow(clippy::unchecked_duration_subtraction)]
+ 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;
+
+ /// # Panics
+ ///
+ /// This function may panic if the resulting point in time cannot be represented by the
+ /// underlying data structure.
+ fn sub(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ #[allow(clippy::unchecked_duration_subtraction)]
+ Self(self.0 - duration.unsigned_abs())
+ } else if duration.is_negative() {
+ Self(self.0 + duration.unsigned_abs())
+ } else {
+ debug_assert!(duration.is_zero());
+ self
+ }
+ }
+}
+
+impl Sub<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;
+
+ /// # Panics
+ ///
+ /// This function may panic if the resulting point in time cannot be represented by the
+ /// underlying data structure.
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ #[allow(clippy::unchecked_duration_subtraction)]
+ 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..2ab59cf8ef
--- /dev/null
+++ b/third_party/rust/time/src/lib.rs
@@ -0,0 +1,373 @@
+//! # 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.
+
+#![doc(html_playground_url = "https://play.rust-lang.org")]
+#![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))]
+#![cfg_attr(coverage_nightly, feature(no_coverage))]
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(
+ anonymous_parameters,
+ clippy::all,
+ clippy::alloc_instead_of_core,
+ clippy::explicit_auto_deref,
+ clippy::obfuscated_if_else,
+ clippy::std_instead_of_core,
+ clippy::undocumented_unsafe_blocks,
+ illegal_floating_point_literal_pattern,
+ late_bound_lifetime_arguments,
+ path_statements,
+ patterns_in_fns_without_body,
+ rust_2018_idioms,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unsafe_op_in_unsafe_fn,
+ unused_extern_crates,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::private_intra_doc_links
+)]
+#![warn(
+ clippy::dbg_macro,
+ clippy::decimal_literal_representation,
+ clippy::get_unwrap,
+ clippy::missing_docs_in_private_items,
+ clippy::nursery,
+ clippy::print_stdout,
+ clippy::todo,
+ clippy::unimplemented,
+ clippy::uninlined_format_args,
+ clippy::unnested_or_patterns,
+ clippy::unwrap_in_result,
+ clippy::unwrap_used,
+ clippy::use_debug,
+ deprecated_in_future,
+ missing_copy_implementations,
+ missing_debug_implementations,
+ unused_qualifications,
+ variant_size_differences
+)]
+#![allow(
+ clippy::redundant_pub_crate, // suggests bad style
+ clippy::option_if_let_else, // suggests terrible code
+ clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released
+ clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released
+)]
+#![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;
+
+// TODO(jhpratt) remove this after a while
+#[cfg(unsound_local_offset)]
+compile_error!(
+ "The `unsound_local_offset` flag was removed in time 0.3.18. If you need this functionality, \
+ see the `time::util::local_offset::set_soundness` function."
+);
+
+// region: macros
+/// Helper macro for easily implementing `OpAssign`.
+macro_rules! __impl_assign {
+ ($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$(
+ #[allow(unused_qualifications)]
+ $(#[$attr])*
+ impl core::ops::$op<$t> for $target {
+ fn $fn(&mut self, rhs: $t) {
+ *self = *self $sym rhs;
+ }
+ }
+ )+};
+}
+
+/// Implement `AddAssign` for the provided types.
+macro_rules! impl_add_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(+ AddAssign add_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `SubAssign` for the provided types.
+macro_rules! impl_sub_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(- SubAssign sub_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `MulAssign` for the provided types.
+macro_rules! impl_mul_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(* MulAssign mul_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `DivAssign` for the provided types.
+macro_rules! impl_div_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(/ DivAssign div_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Division of integers, rounding the resulting value towards negative infinity.
+macro_rules! div_floor {
+ ($a:expr, $b:expr) => {{
+ let _a = $a;
+ let _b = $b;
+
+ let (_quotient, _remainder) = (_a / _b, _a % _b);
+
+ if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) {
+ _quotient - 1
+ } else {
+ _quotient
+ }
+ }};
+}
+
+/// Cascade an out-of-bounds value.
+macro_rules! cascade {
+ (@ordinal ordinal) => {};
+ (@year year) => {};
+
+ // Cascade an out-of-bounds value from "from" to "to".
+ ($from:ident in $min:literal.. $max:expr => $to:tt) => {
+ #[allow(unused_comparisons, unused_assignments)]
+ let min = $min;
+ let max = $max;
+ if $from >= max {
+ $from -= max - min;
+ $to += 1;
+ } else if $from < min {
+ $from += max - min;
+ $to -= 1;
+ }
+ };
+
+ // Special case the ordinal-to-year cascade, as it has different behavior.
+ ($ordinal:ident => $year:ident) => {
+ // We need to actually capture the idents. Without this, macro hygiene causes errors.
+ cascade!(@ordinal $ordinal);
+ cascade!(@year $year);
+ #[allow(unused_assignments)]
+ if $ordinal > crate::util::days_in_year($year) as i16 {
+ $ordinal -= crate::util::days_in_year($year) as i16;
+ $year += 1;
+ } else if $ordinal < 1 {
+ $year -= 1;
+ $ordinal += crate::util::days_in_year($year) as i16;
+ }
+ };
+}
+
+/// Returns `Err(error::ComponentRange)` if the value is not in range.
+macro_rules! ensure_value_in_range {
+ ($value:ident in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: false,
+ });
+ }
+ }};
+
+ ($value:ident conditionally in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: true,
+ });
+ }
+ }};
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is
+/// usable in `const` contexts.
+macro_rules! const_try {
+ ($e:expr) => {
+ match $e {
+ Ok(value) => value,
+ Err(error) => return Err(error),
+ }
+ };
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but is usable in `const` contexts.
+macro_rules! const_try_opt {
+ ($e:expr) => {
+ match $e {
+ Some(value) => value,
+ None => return None,
+ }
+ };
+}
+
+/// Try to unwrap an expression, panicking if not possible.
+///
+/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
+macro_rules! expect_opt {
+ ($e:expr, $message:literal) => {
+ match $e {
+ Some(value) => value,
+ None => crate::expect_failed($message),
+ }
+ };
+}
+
+/// `unreachable!()`, but better.
+macro_rules! bug {
+ () => { compile_error!("provide an error message to help fix a possible bug") };
+ ($descr:literal $($rest:tt)?) => {
+ panic!(concat!("internal error: ", $descr) $($rest)?)
+ }
+}
+// endregion macros
+
+mod date;
+mod date_time;
+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;
+
+// Not public yet.
+use time_core::convert;
+
+pub use crate::date::Date;
+use crate::date_time::DateTime;
+pub use crate::duration::Duration;
+pub use crate::error::Error;
+#[cfg(feature = "std")]
+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..8d8c164b21
--- /dev/null
+++ b/third_party/rust/time/src/month.rs
@@ -0,0 +1,218 @@
+//! 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,
+ }
+ }
+
+ /// Get n-th next month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// assert_eq!(Month::January.nth_next(4), Month::May);
+ /// assert_eq!(Month::July.nth_next(9), Month::April);
+ /// ```
+ pub const fn nth_next(self, n: u8) -> Self {
+ match (self as u8 - 1 + n % 12) % 12 {
+ 0 => January,
+ 1 => February,
+ 2 => March,
+ 3 => April,
+ 4 => May,
+ 5 => June,
+ 6 => July,
+ 7 => August,
+ 8 => September,
+ 9 => October,
+ 10 => November,
+ val => {
+ debug_assert!(val == 11);
+ December
+ }
+ }
+ }
+
+ /// Get n-th previous month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// assert_eq!(Month::January.nth_prev(4), Month::September);
+ /// assert_eq!(Month::July.nth_prev(9), Month::October);
+ /// ```
+ pub const fn nth_prev(self, n: u8) -> Self {
+ match self as i8 - 1 - (n % 12) as i8 {
+ 1 | -11 => February,
+ 2 | -10 => March,
+ 3 | -9 => April,
+ 4 | -8 => May,
+ 5 | -7 => June,
+ 6 | -6 => July,
+ 7 | -5 => August,
+ 8 | -4 => September,
+ 9 | -3 => October,
+ 10 | -2 => November,
+ 11 | -1 => December,
+ val => {
+ debug_assert!(val == 0);
+ January
+ }
+ }
+ }
+}
+
+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..d979174e1d
--- /dev/null
+++ b/third_party/rust/time/src/offset_date_time.rs
@@ -0,0 +1,1196 @@
+//! The [`OffsetDateTime`] struct and its associated `impl`s.
+
+#[cfg(feature = "std")]
+use core::cmp::Ordering;
+#[cfg(feature = "std")]
+use core::convert::From;
+use core::fmt;
+use core::hash::Hash;
+use core::ops::{Add, AddAssign, Sub, SubAssign};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+#[cfg(feature = "std")]
+use std::time::SystemTime;
+
+use crate::date_time::offset_kind;
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::{error, Date, DateTime, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// The actual type doing all the work.
+type Inner = DateTime<offset_kind::Fixed>;
+
+/// A [`PrimitiveDateTime`] with a [`UtcOffset`].
+///
+/// All comparisons are performed using the UTC time.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct OffsetDateTime(pub(crate) Inner);
+
+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 = Self(Inner::UNIX_EPOCH);
+
+ // 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 {
+ Self(Inner::now_utc())
+ }
+
+ /// 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> {
+ Inner::now_local().map(Self)
+ }
+ // 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 {
+ Self(self.0.to_offset(offset))
+ }
+
+ /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`],
+ /// returning `None` if the date-time in the resulting offset is invalid.
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2000-01-01 0:00 UTC)
+ /// .checked_to_offset(offset!(-1))
+ /// .unwrap()
+ /// .year(),
+ /// 1999,
+ /// );
+ /// assert_eq!(
+ /// PrimitiveDateTime::MAX
+ /// .assume_utc()
+ /// .checked_to_offset(offset!(+1)),
+ /// None,
+ /// );
+ /// ```
+ pub const fn checked_to_offset(self, offset: UtcOffset) -> Option<Self> {
+ Some(Self(const_try_opt!(self.0.checked_to_offset(offset))))
+ }
+
+ // 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> {
+ Ok(Self(const_try!(Inner::from_unix_timestamp(timestamp))))
+ }
+
+ /// 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> {
+ Ok(Self(const_try!(Inner::from_unix_timestamp_nanos(
+ timestamp
+ ))))
+ }
+ // 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.0.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 {
+ self.0.unix_timestamp()
+ }
+
+ /// 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.0.unix_timestamp_nanos()
+ }
+
+ /// 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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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(Self(const_try_opt!(self.0.checked_add(duration))))
+ }
+
+ /// 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(Self(const_try_opt!(self.0.checked_sub(duration))))
+ }
+ // 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 {
+ Self(self.0.saturating_add(duration))
+ }
+
+ /// 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 {
+ Self(self.0.saturating_sub(duration))
+ }
+ // 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(self.0.replace_time(time))
+ }
+
+ /// 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(self.0.replace_date(date))
+ }
+
+ /// 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 {
+ Self(self.0.replace_date_time(date_time.0))
+ }
+
+ /// 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(self.0.replace_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(Self(const_try!(self.0.replace_year(year))))
+ }
+
+ /// 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(Self(const_try!(self.0.replace_month(month))))
+ }
+
+ /// 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(Self(const_try!(self.0.replace_day(day))))
+ }
+
+ /// 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(Self(const_try!(self.0.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 +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(Self(const_try!(self.0.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 +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(Self(const_try!(self.0.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 +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(Self(const_try!(self.0.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 +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(Self(const_try!(self.0.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 +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(Self(const_try!(self.0.replace_nanosecond(nanosecond))))
+ }
+}
+// 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> {
+ self.0.format_into(output, format)
+ }
+
+ /// 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> {
+ self.0.format(format)
+ }
+}
+
+#[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> {
+ Inner::parse(input, description).map(Self)
+ }
+}
+
+impl fmt::Display for OffsetDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+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 Add<Duration> for OffsetDateTime {
+ type Output = Self;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ Self(self.0.add(rhs))
+ }
+}
+
+impl Add<StdDuration> for OffsetDateTime {
+ type Output = Self;
+
+ fn add(self, rhs: StdDuration) -> Self::Output {
+ Self(self.0.add(rhs))
+ }
+}
+
+impl AddAssign<Duration> for OffsetDateTime {
+ fn add_assign(&mut self, rhs: Duration) {
+ self.0.add_assign(rhs);
+ }
+}
+
+impl AddAssign<StdDuration> for OffsetDateTime {
+ fn add_assign(&mut self, rhs: StdDuration) {
+ self.0.add_assign(rhs);
+ }
+}
+
+impl Sub<Duration> for OffsetDateTime {
+ type Output = Self;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ Self(self.0.sub(rhs))
+ }
+}
+
+impl Sub<StdDuration> for OffsetDateTime {
+ type Output = Self;
+
+ fn sub(self, rhs: StdDuration) -> Self::Output {
+ Self(self.0.sub(rhs))
+ }
+}
+
+impl SubAssign<Duration> for OffsetDateTime {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.0.sub_assign(rhs);
+ }
+}
+
+impl SubAssign<StdDuration> for OffsetDateTime {
+ fn sub_assign(&mut self, rhs: StdDuration) {
+ self.0.sub_assign(rhs);
+ }
+}
+
+impl Sub for OffsetDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ self.0.sub(rhs.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Sub<SystemTime> for OffsetDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: SystemTime) -> Self::Output {
+ self.0.sub(rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Sub<OffsetDateTime> for SystemTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: OffsetDateTime) -> Self::Output {
+ self.sub(rhs.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<SystemTime> for OffsetDateTime {
+ fn eq(&self, rhs: &SystemTime) -> bool {
+ self.0.eq(rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<OffsetDateTime> for SystemTime {
+ fn eq(&self, rhs: &OffsetDateTime) -> bool {
+ self.eq(&rhs.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<SystemTime> for OffsetDateTime {
+ fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
+ self.0.partial_cmp(other)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<OffsetDateTime> for SystemTime {
+ fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
+ self.partial_cmp(&other.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<SystemTime> for OffsetDateTime {
+ fn from(system_time: SystemTime) -> Self {
+ Self(Inner::from(system_time))
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<OffsetDateTime> for SystemTime {
+ fn from(datetime: OffsetDateTime) -> Self {
+ datetime.0.into()
+ }
+}
+
+#[cfg(all(
+ target_family = "wasm",
+ 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 {
+ Self(Inner::from(js_date))
+ }
+}
+
+#[cfg(all(
+ target_family = "wasm",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+))]
+impl From<OffsetDateTime> for js_sys::Date {
+ fn from(datetime: OffsetDateTime) -> Self {
+ datetime.0.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..23035893e4
--- /dev/null
+++ b/third_party/rust/time/src/parsing/component.rs
@@ -0,0 +1,333 @@
+//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
+
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::convert::*;
+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, n_to_m_digits, 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`.
+///
+/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
+pub(crate) fn parse_offset_hour(
+ input: &[u8],
+ modifiers: modifier::OffsetHour,
+) -> Option<ParsedItem<'_, (i8, bool)>> {
+ let ParsedItem(input, sign) = opt(sign)(input);
+ let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
+ match sign {
+ Some(b'-') => Some(ParsedItem(input, (-(hour as i8), true))),
+ None if modifiers.sign_is_mandatory => None,
+ _ => Some(ParsedItem(input, (hour as i8, false))),
+ }
+}
+
+/// 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
+
+/// Ignore the given number of bytes.
+pub(crate) fn parse_ignore(
+ input: &[u8],
+ modifiers: modifier::Ignore,
+) -> Option<ParsedItem<'_, ()>> {
+ let modifier::Ignore { count } = modifiers;
+ let input = input.get((count.get() as usize)..)?;
+ Some(ParsedItem(input, ()))
+}
+
+/// Parse the Unix timestamp component.
+pub(crate) fn parse_unix_timestamp(
+ input: &[u8],
+ modifiers: modifier::UnixTimestamp,
+) -> Option<ParsedItem<'_, i128>> {
+ let ParsedItem(input, sign) = opt(sign)(input);
+ let ParsedItem(input, nano_timestamp) = match modifiers.precision {
+ modifier::UnixTimestampPrecision::Second => {
+ n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond.per(Second) as u128)
+ }
+ modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
+ .map(|val| val * Nanosecond.per(Millisecond) as u128),
+ modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
+ .map(|val| val * Nanosecond.per(Microsecond) as u128),
+ modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
+ };
+
+ match sign {
+ Some(b'-') => Some(ParsedItem(input, -(nano_timestamp as i128))),
+ None if modifiers.sign_is_mandatory => None,
+ _ => Some(ParsedItem(input, nano_timestamp as _)),
+ }
+}
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..87e517e376
--- /dev/null
+++ b/third_party/rust/time/src/parsing/iso8601.rs
@@ -0,0 +1,322 @@
+//! Parse parts of an ISO 8601-formatted value.
+
+use crate::convert::*;
+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 * Second.per(Minute) as f64) as _)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second(
+ (fractional_part * Second.per(Hour) as f64 % Minute.per(Hour) as f64)
+ as _,
+ )
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * Nanosecond.per(Hour) as f64
+ % Nanosecond.per(Second) as f64) as _,
+ )
+ .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 * Second.per(Minute) as f64) as _)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * Nanosecond.per(Minute) as f64
+ % Nanosecond.per(Second) as f64) as _,
+ )
+ .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 * Nanosecond.per(Second) as f64) 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;
+ };
+ }
+
+ match min(input) {
+ Some(ParsedItem(new_input, min)) => {
+ input = new_input;
+ parsed
+ .set_offset_minute_signed(if sign == b'-' {
+ -(min as i8)
+ } else {
+ min as _
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+ }
+ None => {
+ // Omitted offset minute is assumed to be zero.
+ parsed.set_offset_minute_signed(0);
+ }
+ }
+
+ // If `:` was present, the format has already been set to extended. As such, this call
+ // will do nothing in that case. If there wasn't `:` but minutes were
+ // present, we know it's the basic format. Do not use `?` on the call, as
+ // returning `None` is valid behavior.
+ extended_kind.coerce_basic();
+
+ Ok(input)
+ }
+ }
+}
+
+/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
+/// implementation for `no_std`
+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..78bbe64f0f
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsable.rs
@@ -0,0 +1,760 @@
+//! A trait that can be used to parse an item from an input.
+
+use core::ops::Deref;
+
+use crate::date_time::{maybe_offset_from_offset, MaybeOffset};
+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, DateTime, Month, 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 [`DateTime`] from the format description.
+ fn parse_date_time<O: MaybeOffset>(
+ &self,
+ input: &[u8],
+ ) -> Result<DateTime<O>, 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_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, 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_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, 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 !O::HAS_LOGICAL_OFFSET {
+ false
+ } else 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(DateTime {
+ date,
+ time,
+ offset: maybe_offset_from_offset::<O>(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_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, 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_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, 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 date = Month::from_number(month)
+ .and_then(|month| Date::from_calendar_date(year as _, month, day))
+ .map_err(TryFromParsed::ComponentRange)?;
+ let time = Time::from_hms_nano(hour, minute, second, nanosecond)
+ .map_err(TryFromParsed::ComponentRange)?;
+ let offset = maybe_offset_from_offset::<O>(offset);
+ let dt = DateTime { date, time, offset };
+
+ 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 => bug!("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..04c74a7202
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsed.rs
@@ -0,0 +1,840 @@
+//! Information parsed from an input and format description.
+
+use core::mem::MaybeUninit;
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::date_time::{maybe_offset_from_offset, offset_kind, DateTime, MaybeOffset};
+use 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_ignore, parse_minute, parse_month, parse_offset_hour,
+ parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second,
+ parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period,
+};
+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),
+ }
+ }
+ }
+ }
+}
+
+/// The type of the `flags` field in [`Parsed`]. Allows for changing a single location and having it
+/// effect all uses.
+type Flag = u32;
+
+/// All information parsed.
+///
+/// This information is directly used to construct the final values.
+///
+/// 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: Flag,
+ /// 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>,
+ /// The Unix timestamp in nanoseconds.
+ unix_timestamp_nanos: MaybeUninit<i128>,
+}
+
+#[allow(clippy::missing_docs_in_private_items)]
+impl Parsed {
+ const YEAR_FLAG: Flag = 1 << 0;
+ const YEAR_LAST_TWO_FLAG: Flag = 1 << 1;
+ const ISO_YEAR_FLAG: Flag = 1 << 2;
+ const ISO_YEAR_LAST_TWO_FLAG: Flag = 1 << 3;
+ const SUNDAY_WEEK_NUMBER_FLAG: Flag = 1 << 4;
+ const MONDAY_WEEK_NUMBER_FLAG: Flag = 1 << 5;
+ const HOUR_24_FLAG: Flag = 1 << 6;
+ const MINUTE_FLAG: Flag = 1 << 7;
+ const SECOND_FLAG: Flag = 1 << 8;
+ const SUBSECOND_FLAG: Flag = 1 << 9;
+ const OFFSET_HOUR_FLAG: Flag = 1 << 10;
+ const OFFSET_MINUTE_FLAG: Flag = 1 << 11;
+ const OFFSET_SECOND_FLAG: Flag = 1 << 12;
+ /// Indicates whether a leap second is permitted to be parsed. This is required by some
+ /// well-known formats.
+ pub(super) const LEAP_SECOND_ALLOWED_FLAG: Flag = 1 << 13;
+ /// Indicates whether the `UtcOffset` is negative. This information is obtained when parsing the
+ /// offset hour, but may not otherwise be stored due to "-0" being equivalent to "0".
+ const OFFSET_IS_NEGATIVE_FLAG: Flag = 1 << 14;
+ /// Does the value at `OFFSET_IS_NEGATIVE_FLAG` have any semantic meaning, or is it just the
+ /// default value? If the latter, the value should be considered to have no meaning.
+ const OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED: Flag = 1 << 15;
+ const UNIX_TIMESTAMP_NANOS_FLAG: Flag = 1 << 16;
+}
+
+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(),
+ unix_timestamp_nanos: 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, is_negative)| {
+ self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED, true);
+ self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG, is_negative);
+ self.set_offset_hour(value)
+ })
+ })
+ .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")),
+ Component::Ignore(modifiers) => parse_ignore(input, modifiers)
+ .map(ParsedItem::<()>::into_inner)
+ .ok_or(InvalidComponent("ignore")),
+ Component::UnixTimestamp(modifiers) => parse_unix_timestamp(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_unix_timestamp_nanos(value))
+ })
+ .ok_or(InvalidComponent("unix_timestamp")),
+ }
+ }
+
+ /// Get the value of the provided flag.
+ const fn get_flag(&self, flag: Flag) -> bool {
+ self.flags & flag == flag
+ }
+
+ /// Set the value of the provided flag.
+ pub(super) fn set_flag(&mut self, flag: Flag, value: bool) {
+ if value {
+ self.flags |= flag;
+ } else {
+ self.flags &= !flag;
+ }
+ }
+}
+
+/// Generate getters for each of the fields.
+macro_rules! getters {
+ ($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
+ getters!(! $(@$flag)? $name: $ty);
+ )*};
+ (! $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ self.$name
+ }
+ };
+ (! @$flag:ident $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ if !self.get_flag(Self::$flag) {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.$name.assume_init() })
+ }
+ }
+ };
+}
+
+/// Getter methods
+impl Parsed {
+ getters! {
+ @YEAR_FLAG year: i32,
+ @YEAR_LAST_TWO_FLAG year_last_two: u8,
+ @ISO_YEAR_FLAG iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
+ month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
+ iso_week_number: NonZeroU8,
+ weekday: Weekday,
+ ordinal: NonZeroU16,
+ day: NonZeroU8,
+ @HOUR_24_FLAG hour_24: u8,
+ hour_12: NonZeroU8,
+ hour_12_is_pm: bool,
+ @MINUTE_FLAG minute: u8,
+ @SECOND_FLAG second: u8,
+ @SUBSECOND_FLAG subsecond: u32,
+ @OFFSET_HOUR_FLAG offset_hour: i8,
+ @UNIX_TIMESTAMP_NANOS_FLAG unix_timestamp_nanos: i128,
+ }
+
+ /// Obtain the absolute value of the offset minute.
+ #[doc(hidden)]
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")]
+ pub const fn offset_minute(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs())
+ }
+
+ /// Obtain the offset minute as an `i8`.
+ pub const fn offset_minute_signed(&self) -> Option<i8> {
+ if !self.get_flag(Self::OFFSET_MINUTE_FLAG) {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ let value = unsafe { self.offset_minute.assume_init() };
+
+ if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED)
+ && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG))
+ {
+ Some(-value)
+ } else {
+ Some(value)
+ }
+ }
+ }
+
+ /// Obtain the absolute value of the offset second.
+ #[doc(hidden)]
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")]
+ pub const fn offset_second(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_second_signed()).unsigned_abs())
+ }
+
+ /// Obtain the offset second as an `i8`.
+ pub const fn offset_second_signed(&self) -> Option<i8> {
+ if !self.get_flag(Self::OFFSET_SECOND_FLAG) {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ let value = unsafe { self.offset_second.assume_init() };
+
+ if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED)
+ && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG))
+ {
+ Some(-value)
+ } else {
+ Some(value)
+ }
+ }
+ }
+}
+
+/// 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.set_flag(Self::$flag, true);
+ 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,
+ @UNIX_TIMESTAMP_NANOS_FLAG set_unix_timestamp_nanos unix_timestamp_nanos: i128,
+ }
+
+ /// Set the named component.
+ #[doc(hidden)]
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_minute_signed()` instead"
+ )]
+ pub fn set_offset_minute(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ 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.set_flag(Self::OFFSET_MINUTE_FLAG, true);
+ Some(())
+ }
+
+ /// Set the named component.
+ #[doc(hidden)]
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_second_signed()` instead"
+ )]
+ pub fn set_offset_second(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ 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.set_flag(Self::OFFSET_SECOND_FLAG, true);
+ Some(())
+ }
+}
+
+/// Generate build methods for each of the fields.
+///
+/// This macro should only be used for fields where the value is not validated beyond its type.
+macro_rules! builders {
+ ($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
+ builders!(! $(@$flag)? $builder_name $name: $ty);
+ )*};
+ (! $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = Some(value);
+ Some(self)
+ }
+ };
+ (! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(self)
+ }
+ };
+}
+
+/// Builder methods
+///
+/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if
+/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
+impl Parsed {
+ builders! {
+ @YEAR_FLAG with_year year: i32,
+ @YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
+ @ISO_YEAR_FLAG with_iso_year iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
+ with_month month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
+ with_iso_week_number iso_week_number: NonZeroU8,
+ with_weekday weekday: Weekday,
+ with_ordinal ordinal: NonZeroU16,
+ with_day day: NonZeroU8,
+ @HOUR_24_FLAG with_hour_24 hour_24: u8,
+ with_hour_12 hour_12: NonZeroU8,
+ with_hour_12_is_pm hour_12_is_pm: bool,
+ @MINUTE_FLAG with_minute minute: u8,
+ @SECOND_FLAG with_second second: u8,
+ @SUBSECOND_FLAG with_subsecond subsecond: u32,
+ @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
+ @UNIX_TIMESTAMP_NANOS_FLAG with_unix_timestamp_nanos unix_timestamp_nanos: i128,
+ }
+
+ /// Set the named component and return `self`.
+ #[doc(hidden)]
+ #[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`.
+ #[doc(hidden)]
+ #[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 = <DateTime<offset_kind::None> as TryFrom<Parsed>>::Error;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ parsed.try_into().map(Self)
+ }
+}
+
+impl TryFrom<Parsed> for OffsetDateTime {
+ type Error = <DateTime<offset_kind::Fixed> as TryFrom<Parsed>>::Error;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ parsed.try_into().map(Self)
+ }
+}
+
+impl<O: MaybeOffset> TryFrom<Parsed> for DateTime<O> {
+ type Error = error::TryFromParsed;
+
+ #[allow(clippy::unwrap_in_result)] // We know the values are valid.
+ fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
+ if O::HAS_LOGICAL_OFFSET {
+ if let Some(timestamp) = parsed.unix_timestamp_nanos() {
+ let DateTime { date, time, offset } =
+ DateTime::<offset_kind::Fixed>::from_unix_timestamp_nanos(timestamp)?;
+ return Ok(Self {
+ date,
+ time,
+ offset: maybe_offset_from_offset::<O>(offset),
+ });
+ }
+ }
+
+ // Some well-known formats explicitly allow leap seconds. We don't currently support them,
+ // so treat it as the nearest preceding moment that can be represented. Because leap seconds
+ // always fall at the end of a month UTC, reject any that are at other times.
+ let leap_second_input =
+ if parsed.get_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG) && parsed.second() == Some(60) {
+ parsed.set_second(59).expect("59 is a valid second");
+ parsed
+ .set_subsecond(999_999_999)
+ .expect("999_999_999 is a valid subsecond");
+ true
+ } else {
+ false
+ };
+
+ let dt = Self {
+ date: Date::try_from(parsed)?,
+ time: Time::try_from(parsed)?,
+ offset: O::try_from_parsed(parsed)?,
+ };
+
+ 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..ced7feb03e
--- /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 u128 }
+
+/// 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..9850f96d4d
--- /dev/null
+++ b/third_party/rust/time/src/primitive_date_time.rs
@@ -0,0 +1,885 @@
+//! The [`PrimitiveDateTime`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, AddAssign, Sub, SubAssign};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+use crate::date_time::offset_kind;
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::{error, Date, DateTime, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
+
+/// The actual type doing all the work.
+type Inner = DateTime<offset_kind::None>;
+
+/// Combined date and time.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct PrimitiveDateTime(#[allow(clippy::missing_docs_in_private_items)] pub(crate) Inner);
+
+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(Inner::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(Inner::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(Inner::new(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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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(self.0.assume_offset(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 {
+ OffsetDateTime(self.0.assume_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> {
+ Some(Self(const_try_opt!(self.0.checked_add(duration))))
+ }
+
+ /// 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> {
+ Some(Self(const_try_opt!(self.0.checked_sub(duration))))
+ }
+ // 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 {
+ Self(self.0.saturating_add(duration))
+ }
+
+ /// 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 {
+ Self(self.0.saturating_sub(duration))
+ }
+ // 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(self.0.replace_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 {
+ Self(self.0.replace_date(date))
+ }
+
+ /// 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(Self(const_try!(self.0.replace_year(year))))
+ }
+
+ /// 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(Self(const_try!(self.0.replace_month(month))))
+ }
+
+ /// 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(Self(const_try!(self.0.replace_day(day))))
+ }
+
+ /// 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(const_try!(self.0.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(const_try!(self.0.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(const_try!(self.0.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(const_try!(self.0.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(const_try!(self.0.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(const_try!(self.0.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> {
+ self.0.format_into(output, format)
+ }
+
+ /// 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> {
+ self.0.format(format)
+ }
+}
+
+#[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> {
+ Inner::parse(input, description).map(Self)
+ }
+}
+
+impl fmt::Display for PrimitiveDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+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(self.0.add(duration))
+ }
+}
+
+impl Add<StdDuration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ Self(self.0.add(duration))
+ }
+}
+
+impl AddAssign<Duration> for PrimitiveDateTime {
+ fn add_assign(&mut self, duration: Duration) {
+ self.0.add_assign(duration);
+ }
+}
+
+impl AddAssign<StdDuration> for PrimitiveDateTime {
+ fn add_assign(&mut self, duration: StdDuration) {
+ self.0.add_assign(duration);
+ }
+}
+
+impl Sub<Duration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ Self(self.0.sub(duration))
+ }
+}
+
+impl Sub<StdDuration> for PrimitiveDateTime {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ Self(self.0.sub(duration))
+ }
+}
+
+impl SubAssign<Duration> for PrimitiveDateTime {
+ fn sub_assign(&mut self, duration: Duration) {
+ self.0.sub_assign(duration);
+ }
+}
+
+impl SubAssign<StdDuration> for PrimitiveDateTime {
+ fn sub_assign(&mut self, duration: StdDuration) {
+ self.0.sub_assign(duration);
+ }
+}
+
+impl Sub for PrimitiveDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ self.0.sub(rhs.0)
+ }
+}
+// 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..4a788b517e
--- /dev/null
+++ b/third_party/rust/time/src/quickcheck.rs
@@ -0,0 +1,232 @@
+//! 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::convert::*;
+use crate::date_time::{DateTime, MaybeOffset};
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// Obtain an arbitrary value between the minimum and maximum inclusive.
+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, Hour.per(Day) - 1),
+ arbitrary_between!(u8; g, 0, Minute.per(Hour) - 1),
+ arbitrary_between!(u8; g, 0, Second.per(Minute) - 1),
+ arbitrary_between!(u32; g, 0, Nanosecond.per(Second) - 1),
+ )
+ }
+
+ 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(<_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(self.0.shrink().map(Self))
+ }
+}
+
+impl Arbitrary for UtcOffset {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let seconds =
+ arbitrary_between!(i32; g, -(Second.per(Day) as i32 - 1), Second.per(Day) as i32 - 1);
+ Self::__from_hms_unchecked(
+ (seconds / Second.per(Hour) as i32) as _,
+ ((seconds % Second.per(Hour) as i32) / Minute.per(Hour) as i32) as _,
+ (seconds % Second.per(Minute) as i32) as _,
+ )
+ }
+
+ 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 {
+ Self(<_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(self.0.shrink().map(Self))
+ }
+}
+
+impl<O: MaybeOffset + 'static> Arbitrary for DateTime<O> {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self {
+ date: <_>::arbitrary(g),
+ time: <_>::arbitrary(g),
+ offset: <_>::arbitrary(g),
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.date, self.time, self.offset)
+ .shrink()
+ .map(|(date, time, offset)| Self { date, time, 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..e5181f9bdd
--- /dev/null
+++ b/third_party/rust/time/src/rand.rs
@@ -0,0 +1,100 @@
+//! Implementation of [`Distribution`] for various structs.
+
+use rand::distributions::{Distribution, Standard};
+use rand::Rng;
+
+use crate::convert::*;
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+impl Distribution<Time> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Time {
+ Time::__from_hms_nanos_unchecked(
+ rng.gen_range(0..Hour.per(Day)),
+ rng.gen_range(0..Minute.per(Hour)),
+ rng.gen_range(0..Second.per(Minute)),
+ rng.gen_range(0..Nanosecond.per(Second)),
+ )
+ }
+}
+
+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(-(Second.per(Day) as i32 - 1)..=(Second.per(Day) as i32 - 1));
+ UtcOffset::__from_hms_unchecked(
+ (seconds / Second.per(Hour) as i32) as _,
+ ((seconds % Second.per(Hour) as i32) / Minute.per(Hour) as i32) as _,
+ (seconds % Second.per(Minute) as i32) as _,
+ )
+ }
+}
+
+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..f9e8a20f1e
--- /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_str(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..7e3403fb3b
--- /dev/null
+++ b/third_party/rust/time/src/serde/mod.rs
@@ -0,0 +1,495 @@
+//! 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 a format string or an existing format.
+///
+/// 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)` where `FORMAT` is either a
+/// `"<format string>"` or something that implements
+#[cfg_attr(
+ all(feature = "formatting", feature = "parsing"),
+ doc = "[`Formattable`](crate::formatting::Formattable) and \
+ [`Parsable`](crate::parsing::Parsable)."
+)]
+#[cfg_attr(
+ all(feature = "formatting", not(feature = "parsing")),
+ doc = "[`Formattable`](crate::formatting::Formattable)."
+)]
+#[cfg_attr(
+ all(not(feature = "formatting"), feature = "parsing"),
+ doc = "[`Parsable`](crate::parsing::Parsable)."
+)]
+/// 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
+///
+/// Using a format string:
+///
+/// ```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]");
+///
+/// # #[allow(dead_code)]
+#[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)]"
+)]
+/// struct SerializesWithCustom {
+/// #[serde(with = "my_format")]
+/// dt: OffsetDateTime,
+/// #[serde(with = "my_format::option")]
+/// maybe_dt: Option<OffsetDateTime>,
+/// }
+/// ```
+///
+/// Define the format separately to be used in multiple places:
+/// ```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;
+/// use time::format_description::FormatItem;
+///
+/// const DATE_TIME_FORMAT: &[FormatItem<'_>] = time::macros::format_description!(
+/// "hour=[hour], minute=[minute]"
+/// );
+///
+/// // Makes a module `mod my_format { ... }`.
+/// serde::format_description!(my_format, OffsetDateTime, DATE_TIME_FORMAT);
+///
+/// # #[allow(dead_code)]
+#[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)]"
+)]
+/// struct SerializesWithCustom {
+/// #[serde(with = "my_format")]
+/// dt: OffsetDateTime,
+/// #[serde(with = "my_format::option")]
+/// maybe_dt: Option<OffsetDateTime>,
+/// }
+///
+/// fn main() {
+/// # #[allow(unused_variables)]
+/// let str_ts = OffsetDateTime::now_utc().format(DATE_TIME_FORMAT).unwrap();
+/// }
+/// ```
+///
+/// Customize the configuration of ISO 8601 formatting/parsing:
+/// ```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;
+/// use time::format_description::well_known::{iso8601, Iso8601};
+///
+/// const CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
+/// .set_year_is_six_digits(false)
+/// .encode();
+/// const FORMAT: Iso8601<CONFIG> = Iso8601::<CONFIG>;
+///
+/// // Makes a module `mod my_format { ... }`.
+/// serde::format_description!(my_format, OffsetDateTime, FORMAT);
+///
+/// # #[allow(dead_code)]
+#[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)]"
+)]
+/// struct SerializesWithCustom {
+/// #[serde(with = "my_format")]
+/// dt: OffsetDateTime,
+/// #[serde(with = "my_format::option")]
+/// maybe_dt: Option<OffsetDateTime>,
+/// }
+/// # fn main() {}
+/// ```
+///
+/// [`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() {
+ let Ok(s) = self.format(&DATE_FORMAT) else {
+ return Err(S::Error::custom("failed formatting `Date`"));
+ };
+ return serializer.serialize_str(&s);
+ }
+
+ (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() {
+ let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else {
+ return Err(S::Error::custom("failed formatting `OffsetDateTime`"));
+ };
+ return serializer.serialize_str(&s);
+ }
+
+ (
+ 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() {
+ let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
+ return Err(S::Error::custom("failed formatting `PrimitiveDateTime`"));
+ };
+ return serializer.serialize_str(&s);
+ }
+
+ (
+ 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() {
+ let Ok(s) = self.format(&TIME_FORMAT) else {
+ return Err(S::Error::custom("failed formatting `Time`"));
+ };
+ return serializer.serialize_str(&s);
+ }
+
+ (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::Optional(&FormatItem::Compound(&[
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
+ FormatItem::Optional(&FormatItem::Compound(&[
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetSecond(modifier::OffsetSecond::default())),
+ ])),
+ ])),
+];
+
+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() {
+ let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else {
+ return Err(S::Error::custom("failed formatting `UtcOffset`"));
+ };
+ return serializer.serialize_str(&s);
+ }
+
+ (
+ 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..fcf718bb45
--- /dev/null
+++ b/third_party/rust/time/src/serde/visitor.rs
@@ -0,0 +1,323 @@
+//! 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 mut minutes = 0;
+ let mut seconds = 0;
+
+ if let Ok(Some(min)) = seq.next_element() {
+ minutes = min;
+ if let Ok(Some(sec)) = seq.next_element() {
+ seconds = sec;
+ }
+ };
+
+ 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..3367ebb55c
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/mod.rs
@@ -0,0 +1,28 @@
+//! 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_family = "wasm",
+ 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> {
+ // miri does not support tzset()
+ if cfg!(miri) {
+ None
+ } else {
+ 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..f4a8089328
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/unix.rs
@@ -0,0 +1,157 @@
+//! Get the system's UTC offset on Unix.
+
+use core::mem::MaybeUninit;
+
+use crate::util::local_offset::{self, Soundness};
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Whether the operating system has a thread-safe environment. This allows bypassing the check for
+/// if the process is multi-threaded.
+// This is the same value as `cfg!(target_os = "x")`.
+// Use byte-strings to work around current limitations of const eval.
+const OS_HAS_THREAD_SAFE_ENVIRONMENT: bool = match std::env::consts::OS.as_bytes() {
+ // https://github.com/illumos/illumos-gate/blob/0fb96ba1f1ce26ff8b286f8f928769a6afcb00a6/usr/src/lib/libc/port/gen/getenv.c
+ b"illumos"
+ // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/getenv.c
+ // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/setenv.c
+ | b"netbsd"
+ // https://github.com/apple-oss-distributions/Libc/blob/d526593760f0f79dfaeb8b96c3c8a42c791156ff/stdlib/FreeBSD/getenv.c
+ // https://github.com/apple-oss-distributions/Libc/blob/d526593760f0f79dfaeb8b96c3c8a42c791156ff/stdlib/FreeBSD/setenv.c
+ | b"macos"
+ => true,
+ _ => false,
+};
+
+/// 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(_unix_timestamp: i64, tm: libc::tm) -> Option<UtcOffset> {
+ let seconds = tm.tm_gmtoff.try_into().ok()?;
+ UtcOffset::from_whole_seconds(seconds).ok()
+}
+
+/// 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. The
+/// reason for this 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(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(unix_timestamp: i64, 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 = (local_timestamp - unix_timestamp).try_into().ok()?;
+
+ UtcOffset::from_whole_seconds(diff_secs).ok()
+}
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ // Continue to obtaining the UTC offset if and only if the call is sound or the user has
+ // explicitly opted out of soundness.
+ //
+ // Soundness can be guaranteed either by knowledge of the operating system or knowledge that the
+ // process is single-threaded. If the process is single-threaded, then the environment cannot
+ // be 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 OS_HAS_THREAD_SAFE_ENVIRONMENT
+ || local_offset::get_soundness() == Soundness::Unsound
+ || num_threads::is_single_threaded() == Some(true)
+ {
+ let unix_timestamp = datetime.unix_timestamp();
+ // 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(unix_timestamp) }?;
+ tm_to_offset(unix_timestamp, tm)
+ } else {
+ None
+ }
+}
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..dfbe063a2a
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs
@@ -0,0 +1,13 @@
+use crate::convert::*;
+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) * -(Minute.per(Hour) as i32);
+
+ 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..a4d5882d67
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/windows.rs
@@ -0,0 +1,110 @@
+//! Get the system's UTC offset on Windows.
+
+use core::mem::MaybeUninit;
+
+use crate::convert::*;
+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 = Nanosecond.per(Second) as i64 / 100;
+ ((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 = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system))
+ .try_into()
+ .ok()?;
+
+ UtcOffset::from_whole_seconds(diff_secs).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..030c473bed
--- /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(
+ 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..87b465bb9a
--- /dev/null
+++ b/third_party/rust/time/src/time.rs
@@ -0,0 +1,777 @@
+//! 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;
+
+use crate::convert::*;
+#[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 < Hour.per(Day));
+ debug_assert!(minute < Minute.per(Hour));
+ debug_assert!(second < Second.per(Minute));
+ debug_assert!(nanosecond < Nanosecond.per(Second));
+
+ 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 => Hour.per(Day) - 1);
+ ensure_value_in_range!(minute in 0 => Minute.per(Hour) - 1);
+ ensure_value_in_range!(second in 0 => Second.per(Minute) - 1);
+ 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 => Hour.per(Day) - 1);
+ ensure_value_in_range!(minute in 0 => Minute.per(Hour) - 1);
+ ensure_value_in_range!(second in 0 => Second.per(Minute) - 1);
+ ensure_value_in_range!(millisecond in 0 => Millisecond.per(Second) - 1);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ millisecond as u32 * Nanosecond.per(Millisecond),
+ ))
+ }
+
+ /// 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 => Hour.per(Day) - 1);
+ ensure_value_in_range!(minute in 0 => Minute.per(Hour) - 1);
+ ensure_value_in_range!(second in 0 => Second.per(Minute) - 1);
+ ensure_value_in_range!(microsecond in 0 => Microsecond.per(Second) - 1);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ microsecond * Nanosecond.per(Microsecond) as u32,
+ ))
+ }
+
+ /// 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 => Hour.per(Day) - 1);
+ ensure_value_in_range!(minute in 0 => Minute.per(Hour) - 1);
+ ensure_value_in_range!(second in 0 => Second.per(Minute) - 1);
+ ensure_value_in_range!(nanosecond in 0 => Nanosecond.per(Second) - 1);
+ 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 / Nanosecond.per(Millisecond)) 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 / Nanosecond.per(Microsecond) as u32,
+ )
+ }
+
+ /// 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 / Nanosecond.per(Millisecond)) 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 / Nanosecond.per(Microsecond) as u32
+ }
+
+ /// 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() % Second.per(Minute) as i64) as i8;
+ let mut minutes =
+ self.minute as i8 + (duration.whole_minutes() % Minute.per(Hour) as i64) as i8;
+ let mut hours = self.hour as i8 + (duration.whole_hours() % Hour.per(Day) as i64) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..Nanosecond.per(Second) as _ => seconds);
+ cascade!(seconds in 0..Second.per(Minute) as _ => minutes);
+ cascade!(minutes in 0..Minute.per(Hour) as _ => hours);
+ if hours >= Hour.per(Day) as _ {
+ hours -= Hour.per(Day) as i8;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += Hour.per(Day) as i8;
+ 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() % Second.per(Minute) as i64) as i8;
+ let mut minutes =
+ self.minute as i8 - (duration.whole_minutes() % Minute.per(Hour) as i64) as i8;
+ let mut hours = self.hour as i8 - (duration.whole_hours() % Hour.per(Day) as i64) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..Nanosecond.per(Second) as _ => seconds);
+ cascade!(seconds in 0..Second.per(Minute) as _ => minutes);
+ cascade!(minutes in 0..Minute.per(Hour) as _ => hours);
+ if hours >= Hour.per(Day) as _ {
+ hours -= Hour.per(Day) as i8;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += Hour.per(Day) as i8;
+ 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() % Second.per(Minute) as u64) as u8;
+ let mut minute = self.minute
+ + ((duration.as_secs() / Second.per(Minute) as u64) % Minute.per(Hour) as u64) as u8;
+ let mut hour = self.hour
+ + ((duration.as_secs() / Second.per(Hour) as u64) % Hour.per(Day) as u64) as u8;
+ let mut is_next_day = false;
+
+ cascade!(nanosecond in 0..Nanosecond.per(Second) => second);
+ cascade!(second in 0..Second.per(Minute) => minute);
+ cascade!(minute in 0..Minute.per(Hour) => hour);
+ if hour >= Hour.per(Day) {
+ hour -= Hour.per(Day);
+ 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() % Second.per(Minute) as u64) as i8;
+ let mut minute = self.minute as i8
+ - ((duration.as_secs() / Second.per(Minute) as u64) % Minute.per(Hour) as u64) as i8;
+ let mut hour = self.hour as i8
+ - ((duration.as_secs() / Second.per(Hour) as u64) % Hour.per(Day) as u64) as i8;
+ let mut is_previous_day = false;
+
+ cascade!(nanosecond in 0..Nanosecond.per(Second) as _ => second);
+ cascade!(second in 0..Second.per(Minute) as _ => minute);
+ cascade!(minute in 0..Minute.per(Hour) as _ => hour);
+ if hour < 0 {
+ hour += Hour.per(Day) as i8;
+ 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 => Hour.per(Day) - 1);
+ 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 => Minute.per(Hour) - 1);
+ 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 => Second.per(Minute) - 1);
+ 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 => Millisecond.per(Second) - 1);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ millisecond as u32 * Nanosecond.per(Millisecond),
+ ))
+ }
+
+ /// 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 => Microsecond.per(Second) - 1);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ microsecond * Nanosecond.per(Microsecond) as u32,
+ ))
+ }
+
+ /// 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 => Nanosecond.per(Second) - 1);
+ 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 * Second.per(Hour) as i64
+ + minute_diff as i64 * Second.per(Minute) as i64
+ + second_diff as i64;
+
+ let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 {
+ (seconds - 1, nanosecond_diff + Nanosecond.per(Second) as i32)
+ } else if seconds < 0 && nanosecond_diff > 0 {
+ (seconds + 1, nanosecond_diff - Nanosecond.per(Second) as i32)
+ } 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..af7fd1bf79
--- /dev/null
+++ b/third_party/rust/time/src/utc_offset.rs
@@ -0,0 +1,355 @@
+//! The [`UtcOffset`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::Neg;
+#[cfg(feature = "formatting")]
+use std::io;
+
+use crate::convert::*;
+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() < Minute.per(Hour));
+ debug_assert!(seconds.unsigned_abs() < Second.per(Minute));
+
+ 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 -(Minute.per(Hour) as i8 - 1) => Minute.per(Hour) as i8 - 1
+ );
+ ensure_value_in_range!(
+ seconds in -(Second.per(Minute) as i8 - 1) => Second.per(Minute) as i8 - 1
+ );
+
+ 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 -24 * Second.per(Hour) as i32 - 1 => 24 * Second.per(Hour) as i32 - 1
+ );
+
+ Ok(Self::__from_hms_unchecked(
+ (seconds / Second.per(Hour) as i32) as _,
+ ((seconds % Second.per(Hour) as i32) / Minute.per(Hour) as i32) as _,
+ (seconds % Second.per(Minute) as i32) as _,
+ ))
+ }
+ // 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 * Minute.per(Hour) as i16 + 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 * Second.per(Hour) as i32
+ + self.minutes as i32 * Second.per(Minute) as i32
+ + 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..857f5f20b0
--- /dev/null
+++ b/third_party/rust/time/src/util.rs
@@ -0,0 +1,99 @@
+//! 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,
+ }
+}
+
+#[cfg(feature = "local-offset")]
+/// Utility functions relating to the local UTC offset.
+pub mod local_offset {
+ use core::sync::atomic::{AtomicBool, Ordering};
+
+ /// Whether obtaining the local UTC offset is required to be sound.
+ static LOCAL_OFFSET_IS_SOUND: AtomicBool = AtomicBool::new(true);
+
+ /// The soundness of obtaining the local UTC offset.
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ pub enum Soundness {
+ /// Obtaining the local UTC offset is required to be sound. Undefined behavior will never
+ /// occur. This is the default.
+ Sound,
+ /// Obtaining the local UTC offset is allowed to invoke undefined behavior. **Setting this
+ /// value is strongly discouraged.** To do so, you must comply with the safety requirements
+ /// of [`time::local_offset::set_soundness`](set_soundness).
+ Unsound,
+ }
+
+ /// Set whether obtaining the local UTC offset is allowed to invoke undefined behavior. **Use of
+ /// this function is heavily discouraged.**
+ ///
+ /// # Safety
+ ///
+ /// If this method is called with [`Soundness::Sound`], the call is always sound. If this method
+ /// is called with [`Soundness::Unsound`], the following conditions apply.
+ ///
+ /// - If the operating system provides a thread-safe environment, the call is sound.
+ /// - If the process is single-threaded, the call is sound.
+ /// - If the process is multi-threaded, no other thread may mutate the environment in any way at
+ /// the same time a call to a method that obtains the local UTC offset. This includes adding,
+ /// removing, or modifying an environment variable.
+ ///
+ /// The first two conditions are automatically checked by `time`, such that you do not need to
+ /// declare your code unsound. Currently, the only known operating systems that does _not_
+ /// provide a thread-safe environment are some Unix-like OS's. All other operating systems
+ /// should succeed when attempting to obtain the local UTC offset.
+ ///
+ /// Note that you must not only verify this safety condition for your code, but for **all** code
+ /// that will be included in the final binary. Notably, it applies to both direct and transitive
+ /// dependencies and to both Rust and non-Rust code. **For this reason it is not possible to
+ /// soundly pass [`Soundness::Unsound`] to this method if you are writing a library that may
+ /// used by others.**
+ ///
+ /// If using this method is absolutely necessary, it is recommended to keep the time between
+ /// setting the soundness to [`Soundness::Unsound`] and setting it back to [`Soundness::Sound`]
+ /// as short as possible.
+ ///
+ /// The following methods currently obtain the local UTC offset:
+ ///
+ /// - [`OffsetDateTime::now_local`](crate::OffsetDateTime::now_local)
+ /// - [`UtcOffset::local_offset_at`](crate::UtcOffset::local_offset_at)
+ /// - [`UtcOffset::current_local_offset`](crate::UtcOffset::current_local_offset)
+ pub unsafe fn set_soundness(soundness: Soundness) {
+ LOCAL_OFFSET_IS_SOUND.store(soundness == Soundness::Sound, Ordering::SeqCst);
+ }
+
+ /// Obtains the soundness of obtaining the local UTC offset. If it is [`Soundness::Unsound`],
+ /// it is allowed to invoke undefined behavior when obtaining the local UTC offset.
+ pub fn get_soundness() -> Soundness {
+ match LOCAL_OFFSET_IS_SOUND.load(Ordering::SeqCst) {
+ false => Soundness::Unsound,
+ true => Soundness::Sound,
+ }
+ }
+}
diff --git a/third_party/rust/time/src/weekday.rs b/third_party/rust/time/src/weekday.rs
new file mode 100644
index 0000000000..761107e694
--- /dev/null
+++ b/third_party/rust/time/src/weekday.rs
@@ -0,0 +1,192 @@
+//! 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 n-th next day.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.nth_next(1), Weekday::Tuesday);
+ /// assert_eq!(Weekday::Sunday.nth_next(10), Weekday::Wednesday);
+ /// ```
+ pub const fn nth_next(self, n: u8) -> Self {
+ match (self.number_days_from_monday() + n % 7) % 7 {
+ 0 => Monday,
+ 1 => Tuesday,
+ 2 => Wednesday,
+ 3 => Thursday,
+ 4 => Friday,
+ 5 => Saturday,
+ val => {
+ debug_assert!(val == 6);
+ Sunday
+ }
+ }
+ }
+
+ /// Get n-th previous day.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.nth_prev(1), Weekday::Sunday);
+ /// assert_eq!(Weekday::Sunday.nth_prev(10), Weekday::Thursday);
+ /// ```
+ pub const fn nth_prev(self, n: u8) -> Self {
+ match self.number_days_from_monday() as i8 - (n % 7) as i8 {
+ 1 | -6 => Tuesday,
+ 2 | -5 => Wednesday,
+ 3 | -4 => Thursday,
+ 4 | -3 => Friday,
+ 5 | -2 => Saturday,
+ 6 | -1 => Sunday,
+ val => {
+ debug_assert!(val == 0);
+ 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),
+ }
+ }
+}